Skip to content

pnpm demo:staff exits 1 on a fresh box: service.manager@objectos.ai is denied every CRM object (403 PERMISSION_DENIED) #1779

Closed

Description

@huangyiirene

Summary

pnpm demo:staff — the documented second step of the demo boot procedure (docs/MAINTENANCE.md §4.1) — exits non-zero on a fresh install. Its verify() step signs in as each staffed member and queries crm_account; the fifth member, service.manager@objectos.ai, gets 403 PERMISSION_DENIED, which threw out of main() and aborted the run.

Measured 2026-09-08 against a fresh SQLite dev box (pnpm dev, seeds loaded, pnpm demo:staff --url http://localhost:4059), hotcrm de6ed9e / objectstack 17.3.0:

POST /api/v1/data/crm_account/query -> 403: You do not have permission to perform this action.

It is not confined to crm_account. Signed in as that user, every CRM object answers 403:

object service.manager service.agent na.rep
crm_account 403 200 (9) 200 (6)
crm_opportunity 403 403 200
crm_task 403 200 200
crm_event 403 200 200
crm_lead 403 200 (21) 200
crm_case 403 200 (23) 200

Why it happens

service_manager is a declared position (src/sharing/positions.ts) and it is named by a sharing rule (case_escalation_sharing), which is all test/demo-staffing.test.ts requires — but no permission set is bound to it. The repo has service-agent.profile.ts and no service-manager.profile.ts, and a set is bound to a position by name. So the user holds only the platform's additive member baseline, and that baseline does not open any CRM object door on its own.

The consequence is sharper than a failing script: case_escalation_reassign hands escalating cases to this person and case_escalation_sharing grants them edit on open critical cases — measured on the same box, 35 cases end up owned by service.manager — and they cannot open a single one. The grants are materialised and unreachable.

⚠️ A measured claim in src/sharing/demo-staffing.ts says the opposite and should be re-checked while fixing this: "the OBJECT-LEVEL door on crm_account is opened by member_default, the platform's additive baseline that every org member gets". That was measured on 17.0.0-rc.1. On 17.3.0 an org member holding only service_manager is refused, so either the baseline changed or the reps' access comes from sales_rep / their share rather than from the baseline.

Control leg — this is not new

The base script from origin/main (de6ed9e), run unmodified against the same server, fails identically at the same user with the same 403. So this pre-dates any in-flight change and reproduces on main today.

Not fixed in passing, deliberately

The fix is a permission decision — bind a set to service_manager, reuse service_agent's, or reroute the escalation pool — and the two candidate edits both land on a security surface. #1759's dispatch rules that widening a profile or a permission set to make records visible is out of bounds for a demo-fidelity fix, so this is filed rather than guessed.

What #1759's branch did change is the crash: verify() now reports an unreadable object as a named failure instead of throwing, so the run reaches its own ownership census before exiting 1 with the sentence above. The exit code is still 1, and it stays 1 until this card is decided.

Activity

  1. added
    bugSomething isn't working
    metadataDeclarative metadata — schema, security posture, UI surfaces
    needs-user-decisionNeeds the maintainer's call before work proceeds
    on Sep 8, 2026
  2. added theissue type on Sep 8, 2026
  3. huangyiirene commented on Sep 8, 2026

    @huangyiirene
    CollaboratorAuthor

    First-touch grading → needs-user-decision + bug + metadata + prio:p1, type Bug

    repo:hotcrm seat · session_01PpRjGNnwyo2J1rrmekxB1W · R57 · graded 2026-09-08T11:2xZ.

    ⭐ Filed by #1759's dev, and the refusal to fix it in passing was correct. That card's Zone 1 barred touching any profile, permission set or sharing rule — a demo-fidelity defect must not be repaired by widening a grant. Every candidate fix here lands on exactly that surface, so stopping and filing was the instructed behaviour and the right one.

    Why it goes to the decision box rather than the queue

    Security and permission boundaries sit on the manual floor by name — ⛔ no seat may auto-adjudicate them, and this seat could not this round regardless (CONTRACT_REVIEW_TIER measured unavailable). The three candidate fixes are genuinely different products, not three spellings of one fix.

    Why prio:p1

    Not merely a failing script. Measured on the same box: 35 cases are routed to service.manager by case_escalation_reassign, and that persona can open none of them — the grants are materialised and unreachable. And pnpm demo:staff, the documented second step of the demo boot procedure, exits 1 on a fresh install. Same showcase-fidelity axis as #1759, which is why it carries the same priority.

    ⭐ Control leg makes it a real reading: the unmodified base script from origin/main de6ed9e reproduces the identical 403 at the identical user. ⇒ pre-existing, ⛔ not introduced by #1759's branch.

    四棱卡面

    轴 读数
    实际业务需求 Real and measured, ⛔ not speculative: 35 escalated cases unreachable by their assignee, and the documented boot procedure exits non-zero on a clean box.
    项目长远合理性 ⚠️ The hole is structural: service_manager is a declared position (positions.ts) that a sharing rule names, and test/demo-staffing.test.ts passes on it — yet no permission set binds to it (the repo has service-agent.profile.ts and no service-manager.profile.ts, and sets bind by name). declared ≠ enforced.
    防 AI 写错 ⭐ This is the lane's cardinal shape. Every declaration an author can see says this persona works: the position exists, a rule names it, the suite is green. Only a live 403 says otherwise. An AI copying this pattern would declare a position and believe it grants access. ⇒ the fix should also make the gap detectable, not just closed for this one persona.
    创业阶段不扩散 Splits the options: A adds zero new surface, B adds a profile, C changes product behaviour.

    Options

    • A — bind the existing service_agent permission set to service_manager. Smallest possible change, no new authored surface. ⚠️ Question it must answer: does a service manager legitimately need more than an agent, or is escalation handling the same object access?
    • B — author service-manager.profile.ts. Honest if a manager really is a distinct capability, but it is new surface and ⛔ there is no measured puller for a manager-specific grant today.
    • C — reroute the escalation pool away from service_manager. ⛔ Changes product behaviour and arguably deletes the persona's reason to exist; the escalation demo is the thing it is for.

    Seat recommends A, on the 不扩散 axis with nothing measured pulling toward B. ⛔ Recommendation only — this is the manual floor.

    ⚠️ One thing to re-check while fixing it, ⛔ not a separate card

    src/sharing/demo-staffing.ts carries a measured claim that says the opposite of what is observed today: "the object-level door on crm_account is opened by member_default, the platform's additive baseline that every org member gets". That was measured on 17.0.0-rc.1. On 17.3.0 an org member holding only service_manager is refused. ⇒ either the baseline changed or the reps' access comes from sales_rep/their share rather than the baseline. The comment is load-bearing for whoever picks an option here, so it must be re-measured in the same change rather than inherited.


    维护者速读

    改了什么 —— 还没改,这是一张请你拍板的卡。

    为什么 —— 演示环境里有个「客服经理」角色:升级流程会自动把案例派给他(实测 35 条),共享规则也给了他编辑权 —— 但他打不开任何一个 CRM 对象,全部 403。原因是这个岗位从来没绑过权限集(仓里只有客服专员的,没有客服经理的)。而且 pnpm demo:staff 这条你文档里写的演示启动第二步,在干净机器上直接退出码 1。⚠️ 这不是新问题:用 main 上未改动的脚本跑,一模一样。

    风险与代价(含回滚) —— 今天演示环境本来就是坏的,所以三个选项都是在修,不是在冒险。A 最小(复用现成权限集),B 多一份新配置,C 改产品行为(把升级案例派给别人,那这个角色就没存在理由了)。任何一个都是一次 revert 可回。

    席位意见 —— 荐 A。没有任何实测证据说明「客服经理」此刻需要比「客服专员」更多的权限,创业阶段不扩散。⛔ 但这碰的是权限边界,在人工地板上,我不代裁。⚠️ 另外提醒:无论选哪个,顺手要重测 demo-staffing.ts 里那句 17.0.0-rc.1 时代的旧结论,它现在和实测相反。

    你要做的 —— 选 A、B 还是 C?


    Generated by Claude Code

  4. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    Ruling recorded — option A: bind the existing service_agent permission set to the service_manager position (director seat, decision batch #92, 2026-09-08)

    Provenance (who / verbatim / where): maintainer, live PM chat with the director seat (session_01TezFG8ZMrNH6n5VTNpPpdH), standing delegation 「继续决策」 (2026-09-08T08:3xZ; batches #87–#91 confirmed or run under it) — rule per the presented recommendation; reversible by the maintainer. Recommendation adopted: hotcrm seat 5584353647 (A).

    Ruled. service_manager is a declared position that a sharing rule names and that case_escalation_reassign routes 35 cases to, yet no permission set binds to it — declared ≠ enforced. Bind the existing service_agent set to service_manager. ⛔ B refused: no measured puller for a manager-specific grant, and a new profile is new surface (axis ④ 「先专注于核心能力」). ⛔ C refused: rerouting the escalation pool deletes the persona's reason to exist.

    Execution constraints (Zone 1 for the dispatch):

    1. The binding is the only security-surface edit; ⛔ no widening of service_agent's grants and ⛔ no new profile file.
    2. Re-measure the demo-staffing.ts claim that member_default opens the object-level door on crm_account (measured on 17.0.0-rc.1) on 17.3.0 in the same change; correct the comment to what is observed. If the re-measure shows the reps' access comes from sales_rep/their share, say so in the PR body.
    3. Acceptance: pnpm demo:staff exits 0 on a fresh box; service.manager opens every CRM object in the card's table with 200; the 35 escalated cases are readable by their assignee; the ownership census still runs.
    4. Reversal path: if a customer needs a manager grant that differs from an agent's, it comes back as a product card with the puller named.

    needs-user-decision → pm:queue.


    Generated by Claude Code

  5. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionNeeds the maintainer's call before work proceeds
    on Sep 8, 2026
  6. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 8, 2026
  7. self-assigned this
    on Sep 8, 2026
  8. huangyiirene commented on Sep 8, 2026

    @huangyiirene
    CollaboratorAuthor

    Claim: PM loop round R57 (wave 4 — ruled cards)
    Session: session_01PpRjGNnwyo2J1rrmekxB1W
    Branch: claude/issue-1779-service-manager-permission-set
    Worktree: hotcrm-issue-1779
    Domain: repo:hotcrm (single-lane repo — no domain:*)
    File surface: the service_manager position→permission-set binding + src/sharing/demo-staffing.ts (the re-measured comment) + .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgement tier — ⚠️ ⛔ not a --tier reading: dispatch-gates.mjs REFUSES for this repo
    Clause-②: no — app metadata in a private: true repo; ⛔ no published contract surface moves. ⚠️ The manual-floor aspect (a permission boundary) is real and is discharged by the ruling below.
    Thread-read: 5584682186
    Serial constraints cleared: src/sharing/** was released by #1759 (landed c78dc55). Dispatched alongside #1714 (src/data/sales.seed.ts) and #1775 (test/flow-record-change.test.ts) — all three file surfaces disjoint, verified card by card. git ls-remote: 0 branches for this card.

    Ruled — director seat, decision batch #92 (5584682186)

    Option A: bind the existing service_agent permission set to the service_manager position. ⛔ B refused (no measured puller for a manager-specific grant; a new profile is new surface). ⛔ C refused (rerouting the escalation pool deletes the persona's reason to exist). Provenance: maintainer, live PM chat with the director seat, standing delegation 「继续决策」; recommendation adopted from this seat's 5584353647.

    The ruling's four execution constraints travel into the dispatch as Zone 1 verbatim, including its acceptance: pnpm demo:staff exits 0 on a fresh box · service.manager opens every CRM object in the card's table with 200 · the 35 escalated cases are readable by their assignee · the ownership census still runs.

    ⚠️ Ruling item 2 is not optional: the member_default claim in demo-staffing.ts was measured on 17.0.0-rc.1 and contradicts what 17.3.0 does. It must be re-measured in this same change, ⛔ not inherited.


    Generated by Claude Code

  9. huangyiirene commented on Sep 8, 2026

    @huangyiirene
    CollaboratorAuthor

    os-dev-report

    {
      "gates": "pnpm verify EXIT 0 — validate && typecheck && lint && lint:i18n-gate && hygiene && hygiene:tokens && build && test. Exit code captured before any pipe; wrapper judgement line: 'os-verify-lock: VERDICT command-exit 0 · held the lock 141s (2m21s) · waited 99s'. vitest: 'Test Files 164 passed (164)' / 'Tests 3439 passed | 1 skipped (3440)' — test file count UNCHANGED at 164, no test file added. scripts/pm/dispatch-gates.mjs REFUSES for this repo, so pnpm verify is the whole gate surface and nothing was derived from it. FIRST verify run exited 1 on test/docs-declared-versions.test.ts ('Permissions: page says 6, the stack registers 7'); figure re-read from pnpm validate into docs/STATUS.md, second run green. RULED ACCEPTANCE, all four measured on a fresh box (.objectstack/data wiped, rebuilt, reseeded): (1) pnpm demo:staff EXIT 0 — control leg on the untouched tree at 9371173 exited 1 with the card's own sentence, and the ablated leg (one export line removed, absence proved in dist/objectstack.json before boot) exited 1 again; (2) service.manager@objectos.ai per-object: crm_account 403 to 200(9), crm_task 403 to 200(0), crm_event 403 to 200(0), crm_lead 403 to 200(21), crm_case 403 to 200(8), crm_opportunity 403 to 403 — NOT MET for crm_opportunity, see deviations[0]; (3) escalated cases readable by their assignee: 3 cases escalated live, 3 handed to service.manager by case_escalation_reassign, 0 of 3 unreadable (403 on crm_case outright before the change); (4) the #1759 ownership census runs and passes, 23/21/7/27 routed rows all landing on their routed persona, 0 on the dev admin, 0 ownerless.",
      "line_budget": "APPLIES (src/sharing/** and src/profiles/** are both inside the ratchet surface). From the gate's own output line — BEFORE: 'authored total 155 files 14,599 lines 548,206 chars 137,052 ~tokens', 'authored total ~137,052 tokens (ceiling ~140,000; headroom ~2,948)'. AFTER: 'authored total 155 files 14,605 lines 548,394 chars 137,099 ~tokens', 'authored total ~137,099 tokens (ceiling ~140,000; headroom ~2,901)'. DELTA +47 tokens, +6 lines; headroom now 2,901 of ~140,000. Both headline layers byte-identical (business semantics ~84,609 ceiling ~100,000; interaction layer ~37,844 ceiling ~40,000) — the whole delta lands in the residual. hygiene:tokens EXIT 0, 'source token ratchet clean'. No ceiling line was touched and no re-anchor was offered or taken.",
      "deviations": [
        "ACCEPTANCE ITEM NOT MET, reported rather than forced (Zone 2 item 3, stop-and-report). Zone 1 item 3 asks for 200 on EVERY object in the card's table; crm_opportunity stays 403 for service.manager and CANNOT be 200 under Zone 1 item 1. ServiceAgentProfile.crm_opportunity is allowRead:false by design, so a Service Agent is 403 there too — on main and on this branch, exactly as the card's own table records. Reaching 200 needs either widening service_agent's grants (refused by constraint 1) or a manager-only grant (option B, refused). Measured layer: 'object_crud denies — No resolved permission set grants read on crm_opportunity.' The other five objects all moved 403 to 200. Needs a seat decision: does a Service Manager read the pipeline? If yes it is the ruling's own item-4 reversal path, a product card with the puller named.",
        "MECHANISM: Zone 2 item 2 asked how the binding is declared here. Measured, not inferred — there is NO authorable binding on either side. PermissionSetSchema refuses profiles/roles/users; PositionSchema refuses permissionSets and says capability arrives only through sys_position_permission_set rows 'created in Setup or by an app kernel:ready binder', neither of which a pure-metadata app ships. On a fresh 17.3.0 box sys_position_permission_set holds exactly ONE row (everyone to member_default) while security/explain still credits a service_agent position holder's grant to [service_agent] via position:service_agent. resolvePermissionSets() in @objectstack/plugin-security matches POSITION NAMES against declared PermissionSet.name. So the only spelling available is a set declared under the position's name: ServiceManagerProfile = { ...ServiceAgentProfile, name: 'service_manager', label: 'Service Manager' }, objects and fields carried BY REFERENCE so the two personas share one grant table and cannot drift. No grant widened, no new profile file, no sharing rule touched.",
        "FILE SURFACE: docs/STATUS.md is outside the declared surface (binding + src/sharing/demo-staffing.ts + .changeset/). It is in the diff because test/docs-declared-versions.test.ts went RED on my change — 'Permissions: page says 6, the stack registers 7' — i.e. this PR made a published figure false and the guard demanded it. One number, re-read from pnpm validate. Declared rather than silent.",
        "CHANNEL: repo-scoped REST is 403 in this container ('GitHub access is not enabled for this session'), so all GitHub reads and writes went through MCP. mcp_calls = 5 (issue_read get, issue_read get_comments, create_pull_request, pull_request_read, this comment). No search_issues was spent: nothing here met the file-a-card bar.",
        "RE-MEASURE (Zone 1 item 2) INVERTED THE OLD CLAIM, and the PR body says so as instructed. The reps' access does NOT come from member_default. Building the exact principal the old note quotes — a probe user holding ONLY eu_sales_team, every sharing rule re-evaluated — 17.3.0 answers 'object_crud DENIES — No resolved permission set grants read on crm_account' and the query returns 403 PERMISSION_DENIED, where 17.0.0-rc.1 recorded 'grants ... by [member_default]'. The door is opened by sales_rep, the set carrying the rep position's name; member_default opens no CRM object at all. The row-set half of the old note survives and is kept (private OWD plus the territory rule's shares: 6 NA / 2 EU / SG for nobody).",
        "noted, not filed: test/demo-staffing.test.ts carries the SAME 17.0.0-rc.1 claim this PR retires, in two comments ('a user holding only eu_sales_team still reads the 2 EU accounts — the object door is opened by the platform's additive member_default baseline', and the depth note naming member_default as a granting set for crm_account). Both now measurably false. LEFT UNTOUCHED: test/** is epic #1579's declared territory and outside this dispatch's file surface. Hazard is concrete — on that false premise the assertion directly above it (a territory rep must also hold a functional position) reads as redundant and invites deletion. 承接者: whoever next edits that file, or the seat pricing #1579.",
        "noted, not filed: src/profiles/index.ts's canonical allowTransfer note says the bit goes 'only to system_admin and sales_manager' and that 'service agents hold no transfer grant', while ServiceAgentProfile has held crm_task.allowTransfer since #592 and test/ownership-model.test.ts pins that as correct. Pre-existing inconsistency, unchanged by this PR. 承接者: the next edit to that canonical note.",
        "no new test file added, per Zone 2 item 6. None is needed: the ruled acceptance is a script run, and the binding is already guarded by test/demo-staffing.test.ts ('every staffed position is named by a sharing rule or a permission set'), which now sees service_manager as referenced. If the seat wants a pin that a position holder actually resolves a set, that is a live-server assertion this suite has no harness for and should be priced separately."
      ],
      "files_changed": [
        "src/profiles/service-agent.profile.ts (+58) — ServiceManagerProfile: the same grant tables, by reference, under the position's name; the ruling, the measured mechanism and the reversal path recorded beside it",
        "src/profiles/index.ts (+5) — export it from the barrel that objectstack.config.ts registers, with a pointer to the note",
        "src/sharing/demo-staffing.ts (+37 -16) — the 17.0.0-rc.1 member_default claim replaced by the 17.3.0 measurement, with the explain layer stack it came from",
        "docs/STATUS.md (+1 -1) — 6 Permissions to 7, demanded by test/docs-declared-versions.test.ts",
        ".changeset/service-manager-can-open-the-cases-it-is-handed.md (+58) — a real changeset, not skip-changeset: this moves the app's declared permission shape, so a marketplace consumer materialises a seventh permission set and one persona goes from denied to granted"
      ]
    }

    Branch claude/issue-1779-service-manager-permission-set · draft PR #1788 · base main (branched at 9371173) · commits 96371e9, b8534759.


    Generated by Claude Code

  10. huangyiirene commented on Sep 8, 2026

    @huangyiirene
    CollaboratorAuthor

    ✅ ACCEPT — PR #1788. ⚠️ One ruled acceptance item is unreachable under the ruled option, and that is a finding, not a failure

    session_01PpRjGNnwyo2J1rrmekxB1W, R57 wave 4, readings 2026-09-08T13:1xZ.

    ⭐ The mechanism was measured, and it is the reason this card was hard

    Zone 2 asked how a set binds to a position here. The answer is that it does not — there is no authorable binding on either side. PermissionSetSchema rejects profiles/roles/users; PositionSchema rejects permissionSets and says capability arrives only via sys_position_permission_set rows created in Setup or by a kernel:ready binder — neither of which a pure-metadata app ships.

    What it can ship is a name: resolvePermissionSets() matches the caller's position names against declared PermissionSet.name. Measured on a fresh 17.3.0 box — sys_position_permission_set held exactly one row (everyone → member_default) while security/explain still credited the grant to [service_agent] via position:service_agent.

    ⇒ ServiceManagerProfile = { ...ServiceAgentProfile, name: 'service_manager', label: 'Service Manager' }, with objects and fields carried by reference. ⭐ That is the ruling's "bind the existing set" executed in the only spelling the platform offers — and the by-reference spread makes ⛔ "no widening of service_agent's grants" structurally true rather than merely observed: the two personas share one grant table and cannot drift.

    ⚠️ Acceptance item 2 not met on crm_opportunity — verified by this seat, and the ruling is what was over-specified

    The ruling asked for 200 on every object in the card's table. Five moved: crm_account · crm_task · crm_event · crm_lead · crm_case, all 403 → 200. crm_opportunity stays 403, and ⛔ cannot be anything else under Zone 1 item 1. Confirmed independently on origin/main:

    src/profiles/service-agent.profile.ts:19
      crm_opportunity: { allowCreate: false, allowRead: FALSE, … }
    

    ⇒ A Service Agent is 403 there too — which the card's own table records (crm_opportunity | 403 | 403 | 200). Binding the agent's set to the manager's position gives the manager exactly the agent's grants; it cannot grant a read the agent does not have. The acceptance item and the chosen option were inconsistent from the start, and no one noticed until it was measured.

    ⭐ The dev stopped and reported instead of reaching for the fix — which would have been widening service_agent (refused by constraint 1) or a manager-only grant (option B, refused). That is the stop-and-report clause working exactly as intended.

    ⇒ ⛔ Not a REWORK and ⛔ not re-adjudicated. The residual question — does a Service Manager read the pipeline? — is precisely the ruling's own item 4 reversal path: it returns as a product card with the puller named, and there is no puller today. ⚠️ Flagged to the director seat as an FYI, since a ruled acceptance criterion is being recorded as unmet by construction.

    ⭐ Zone 1 item 2 inverted the claim it was sent to check

    The demo-staffing.ts note recorded, from 17.0.0-rc.1, that member_default opens the object door on crm_account for every org member. Re-measured on 17.3.0 and inverted: a principal holding only eu_sales_team resolves to member_default alone and is DENIED (No resolved permission set grants read on 'crm_account'; the query answers 403). The door is opened by sales_rep — the set carrying the rep position's name. The row-set half of the old note survives and is kept.

    ⭐ The note now carries the layer stack it was measured from, so the next reader can re-run it rather than trust it. This is the same name-matching mechanism the card's own fix turns on — which is why the two halves of this PR belong together.

    Checklist verdicts

    item verdict
    PR form draft ✓ · base main ✓ · Fixes #1779 ✓
    ⛔ no widened grant, no new profile file, no sharing rule ✓ — by-reference spread inside the existing file; src/sharing/demo-staffing.ts changes are comment lines only
    ⛔ no test file added ✓ 164 → 164
    gates 8/9 success; Build and Test (22.x) converging ⇒ ⛔ ready-flip withheld
    Clause-② app metadata in a private: true repo ⇒ no; the manual-floor aspect is discharged by the ruling
    path face src/** · docs/** · .changeset/** ⇒ ⛔ not governed ⇒ normal landing
    line_budget APPLIES, +47 tokens / +6 lines ⇒ headroom 2,948 → 2,901 of ~140,000. Both headline layers byte-identical; the delta lands in the residual

    ⭐ The first pnpm verify run exited 1 — test/docs-declared-versions.test.ts: "Permissions: page says 6, the stack registers 7". The PR had made a published figure false and the repo's own guard demanded the fix. docs/STATUS.md is outside the declared file surface and the dev declared it rather than slipping it in; one number, re-read from pnpm validate. ⇒ correct, and a good sign the guard farm is doing its job.

    ⭐ The acceptance was measured on a fresh box with a control leg: the untouched tree at 9371173 exits 1 with the card's own sentence, and an ablated leg (one export line removed, absence proved in dist/objectstack.json before boot) exits 1 again. ⇒ the 0 is caused by this change, not by the rebuild.

    Out-of-scope findings

    1. ⚠️ Filed by this seat — test/demo-staffing.test.ts carries the same retired 17.0.0-rc.1 claim this PR corrects, at :190-194 and again at :213. Verified verbatim in source by this seat. It is now measurably false, and the hazard the dev names is concrete: on that false premise the assertion directly above it (a territory rep must also hold a functional position) reads as redundant and invites deletion. ⇒ that is a live trap for the next author, ⛔ not a suspicion — see the new card. Correctly left untouched here: test/** is epic Epic: bring this repo's test farm back under the 2026-08-31 ruling — platform-first, os lint --strict first, then retire the local re-implementations by family #1579's territory and outside this dispatch's surface.
    2. src/profiles/index.ts's canonical allowTransfer note says service agents hold no transfer grant while ServiceAgentProfile has held crm_task.allowTransfer since Activity model: first-class meetings/events + call logging on every sales object #592 (and test/ownership-model.test.ts pins that as correct). Pre-existing, untouched, successor named ⇒ ⛔ not filed.

    ⇒ ACCEPT. Flipping ready and queueing once the last gate reports success.


    Generated by Claude Code

  11. added
    priority:p1High: required for production / M2
    and removed on Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingmetadataDeclarative metadata — schema, security posture, UI surfacespm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p1High: required for production / M2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions