Skip to content

Replace the hand-built hook / flow / action harnesses with @objectstack/verify's in-process handle; delete the stand-ins and the suites that only prove the stand-ins; declare the platform packages tests import (epic #1579, step 5) #1595

Closed

Description

@os-zhuang

Blocked-by: objectstack-ai/objectstack#15951

Sub-issue of the epic #1579 (step 5). Filed by the director seat (objectstack#12708, summon #15); pm:epic reserves it for the epic PM. ⛔ Not dispatchable until the Blocked-by clears on the install surface: per this lane's charter and AGENTS.md § Scope rule 2, the handle must be in a published @objectstack/verify that this repo pins (probe: the pinned package's dist/index.d.ts exports the handle), not merely merged.

Ruling (maintainer, 2026-09-05, live director chat, verbatim)

「新增平台公开面」 → 「我认为平台的能力应该放在平台,但是平台该怎么设计提供这个能力,你需要完整的重新考虑」 → 「是不是 把执行能力并进 @objectstack/verify / os test 更合理?」 → design B′ → 「同意」 → 「应该还是刚才 hotcrm 专题卡的子卡片吧」. The platform card is objectstack#15951; this card is the hotcrm consequence.

Governing text. AGENTS.md § "Scope — a pure metadata application (2026-08-31 ruling)": rule 1 "⛔ Do not re-invent what the platform already owns"; rule 2 "⛔ Do not route around it — no defensive coding, no shape tolerance, no hand-written predicate re-implementing a rule that lives inside the platform"; rule 3 "Tests in this repo pin this repo's own business facts and nothing else".

Measured (origin/main 021db549, 2026-09-05T15:4xZ)

test/helpers/ — what each stand-in fakes and what it delegates:

helper lines / importers delegates to the real engine re-implements
hook-harness.ts 618 / 38 ctx.input Proxy via wrapDeclarativeHook (objectql); ADR-0104 shape rules via @objectstack/spec/data ctx.api over plain arrays with a hand-written Mongo-ish matches(); makeCtx() HookContext; no permission check
flow-harness.ts 720 / 20 AutomationEngine + installBuiltinNodes (service-automation); column shape via applySystemFields the driver (array store, own $gt/$lt), hook dispatch order (sort by priority), a stub messaging service; no permission check
action-sandbox.ts 407 / 17 QuickJSScriptRunner + actionBodyRunnerFactory / hookBodyRunnerFactory (runtime); the CLI's body extractor a stub ObjectQL engine that copies measured kernel rules (pinned by test/action-sandbox.test.ts)
metadata-fixtures.ts 171 / 18 package rosters from platform-objects, plugin-approvals, plugin-sharing, service-* a hand-listed AUDIT_PROVISIONED
tenancy-probe.ts 86 / 6 — a hand-written plugin registering a fake tenancy service
repo-root.ts / heading-label.ts / docs-anchors.ts 15 / 117 / 286 — docs tooling, not execution — step 6, frozen, stays

The same repo already boots a real ObjectKernel in 10 tests and drives real ObjectQL over InMemoryDriver / SqliteWasmDriver in ~53 — the stand-ins exist only because no convenient handle on that real engine existed. docs/audits/2026-09-hotcrm-handwritten-test-split.md (objectstack) sizes the deletable family at 4,069 lines: the four harness helpers plus the five suites that exist only to prove the stand-ins match the engine — flow-harness-declared-columns, harness-lookup-shape, hook-input-shape, hook-write-shape, hook-query-predicate.

Dependency hygiene: tests and helpers import @objectstack/core, platform-objects, plugin-security, plugin-sharing, plugin-approvals, service-messaging, service-storage — none declared in package.json; they resolve as transitive deps by luck.

The work (after the pin carries objectstack#15951)

  1. Add @objectstack/verify at the repo's pinned exact version (no ^, like every other @objectstack/* here). Boot once per worker with the promoted shared-boot helper; ⛔ do not write a local memo.
  2. Port the importers, one commit per helper: hook-harness (38 files) → hooks.run / seed / rows; flow-harness (20) → flows.run / flows.resume; action-sandbox (17) → actions.run; metadata-fixtures (18) → the handle's metadata; tenancy-probe (6) → BootOptions.tenancy. Each ported test must pin the same business fact it pinned before — the diff moves the means, never the claim.
  3. Delete the four harness helpers, metadata-fixtures.ts, and the five stand-in proof suites named above. test/action-sandbox.test.ts goes with the sandbox it pins.
  4. Declare every @objectstack/* package a remaining test imports, or drop the import; the aim is that @objectstack/verify is the only test-side platform entry besides spec.
  5. A behaviour the handle cannot express is a platform finding: file it on objectstack citing this card, keep that one local helper path until the fix is pinned, and report the count. ⛔ Never re-grow a local stand-in to cover the gap.
  6. Changeset: empty frontmatter (tests only; no user-visible metadata change).

Acceptance

  • ls test/helpers/ contains only repo-root.ts, heading-label.ts, docs-anchors.ts, persona-vocabulary.ts (step 6 material).
  • pnpm test green; the 86 business-fact files still assert the same facts (reviewer spot-checks the five exemplars named on objectstack#15951: hooks-runtime-sales, flow-quote, global-actions, sla-at-risk-live-work, unassigned-case-triage-reach).
  • Coverage thresholds in vitest.config.ts unchanged and met.
  • package.json declares every @objectstack/* package any test imports (git grep -h "from '@objectstack/" test | sort -u vs package.json).
  • Zero tests deleted that pin a business fact — deletions are exactly the stand-ins and their self-proofs.

Not in this card

Any test/** retirement covered by the F-family cards #1582–#1587 (those wait on os lint --strict, #1581); the docs-tooling helpers (step 6); anything in objectstack.

Refs: #1579 (epic; census 5552607309) · objectstack#15951 (step 5a, the handle) · objectstack#15952 (step 5b, docs + scaffold) · objectstack#15953 (step 5c, derived proof families) · objectstack#13848.

Activity

  1. added
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    on Sep 5, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    on Oct 8, 2026
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Ruling: batch hotcrm-R74 item 4 · letter B · maintainer 「同意」 2026-10-08T03:14Z

    repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3. Epic #1579 is dissolved by this ruling; the full reading is on #1579. This card leaves the subtree and joins the repo:hotcrm lane.

    Transition: no pm:* state → pm:queue · reason: the Blocked-by: objectstack-ai/objectstack#15951 above is closed (2026-09-10), and the handle has shipped in the pinned @objectstack/verify 17.7.0. Its .d.ts carries hooks.run and actions.run, and its CHANGELOG has the entry "Every VerifyStack now carries an in-process handle on the stack bootStack boots". pm:epic is removed in the same write.

    Measured now (origin/main c967803): the five stand-ins are still in place, test/helpers/{hook-harness,flow-harness,action-sandbox,metadata-fixtures,tenancy-probe}.ts = 2,045 lines before their self-proof suites. Item 4 of this card (declare the platform packages test/ imports) already landed as #1769.

    Serial order: after hotcrm#2000, because both edit test files. The epic's rule 2 still binds: a behaviour the handle cannot express is a platform gap, filed upstream; that one helper path is kept, and no stand-in is re-grown.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 8, 2026
  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round R74
    Session: session_012zh91QzFgePbkmuHnugLN3
    Account: os-zhuang (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-1595-verify-handle
    Worktree: hotcrm-issue-1595
    Domain: repo:hotcrm (single-lane repo, no domain:* taxonomy)
    Seat: repo:hotcrm#1
    File surface: package.json / pnpm-lock.yaml (declare @objectstack/verify and every @objectstack/* a remaining test imports, at the exact pin); test/helpers/{hook-harness,flow-harness,action-sandbox,metadata-fixtures,tenancy-probe}.ts (deleted once ported); the six stand-in proof suites test/{flow-harness-declared-columns,harness-lookup-shape,hook-input-shape,hook-write-shape,hook-query-predicate,action-sandbox}.test.ts (deleted); the test files importing those five helpers (about 119 on 99d290ad), ported to the @objectstack/verify in-process handle; one .changeset/1595-*.md (empty frontmatter). ⛔ No src/** change (stop on breach; explain in the report)
    Container & model: L, mode:subagent (the os-dev definition and the scripts/pm write tools live in this container's objectstack checkout), model: default (dispatch-gates --tier --repo objectstack-ai/hotcrm answers "no path-derived mandate … the tier stays the PM's per-card judgment call")
    Clause-②: no
    Responsibility: n/a — not a defect card
    Thread-read: 6051426954
    Serial constraints cleared: no open PR in the repo; the fold retirement (PR #2012, 99d290ad) on the shared suites has landed. #2000 is pm:blocked with its branch c8f331cf editing 8 engine suites and adding test/helpers/stack-picklists.ts; it merges main on resume. #1919 (comments across src/ and some tests) waits for this card. Ruling-ref: hotcrm-R74 item 4 B (6051426954).


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 1595,
    "status": "done",
    "branch": "claude/issue-1595-verify-handle",
    "pr": "#2013",
    "session": "session_012zh91QzFgePbkmuHnugLN3 — this run's harness-stamped id (subagent of the PM seat repo:hotcrm, round R74)",
    "premise_still_valid": true,
    "summary": "Every importer of the five stand-ins (hook-harness 41, flow-harness 26, action-sandbox 19, metadata-fixtures 18, tenancy-probe 6 at base 99d290a) now boots the shipped app through @objectstack/verify 17.7.0 and drives it through the platform's doors: hooks.run / seed / rows, flows.run / flows.resume, actions.run, metadata, bootStack (one commit per helper: e0a8577, 69a2606, 49b229a, c593a86, 13d8d74; follow-ups 1f952f9 docs, 330517f changeset, 632787a runtime-coverage names). The 5 helpers and 7 self-proof suites are deleted (the 6 named + sharing-posture-declaration, the tenancy-probe's own guard, measured empty after the port); @objectstack/verify, plugin-audit and plugin-email are declared at exact 17.7.0, and every @objectstack/* a test imports is declared. Each test pins the same fact. Where the real engine cannot reproduce a stand-in setup, the change is listed in the PR body and commented beside the test. 16 measured defects surfaced and are reported below (the defects contradicting a base assertion are pinned as '⚠️ measured defect' cases with a rewrite-me message). Deviations: test/helpers keeps verify-stack.ts (8 platform-gap paths) plus 6 static readers (composed-stack, src-roster, config-globs, flow-regions, registration-lists, identity-objects) beyond the card's four; AGENTS.md's two citations of deleted suites were repointed (1f952f9); guestInsert moved to the anonymous form door's real context, which changed two tests pushed in earlier commits (guest-submission-sanitisation, flow-record-change); no src/** change; the harness attribution reminder naming a model was not imitated — commits carry the model-free trailers, the PR footer uses the session-URL form.",
    "tests": "Final commit 632787a (git rev-parse --short HEAD echoed by both runs), both under OS_VERIFY_LOCK_SLOT=hotcrm-1595 os-verify-lock.sh. pnpm verify: 'os-verify-lock: VERDICT command-exit 0 · held the lock 493s'; validate '✓ Validation passed'; typecheck clean (tsc --listFiles covers 173 test/.test.ts); lint (objectstack lint --strict) '18 suggestion(s)', 0 errors 0 warnings; lint:i18n-gate '✓ i18n lint gate: 0 i18n/missing- issues'; hygiene '✓ source hygiene clean'; hygiene:tokens '✓ source token ratchet clean'; build '✓ Build complete' ('all 42 callables are body-only', 8 pre-existing approval-approvers-may-resolve-empty author warnings); test 'Test Files 172 passed (172) · Tests 3563 passed | 1 skipped (3564)'. pnpm test --coverage: 'VERDICT command-exit 0', same counts; coverage (src//objects/.hook.ts) Statements 96.11% (base 95.5), Branches 86.7% (85.45), Functions 97% (96), Lines 99.5% (99.4); vitest.config.ts thresholds 95/92/92/78 unchanged and met. An earlier pnpm verify on 330517f was red only in runtime-coverage (2 cases: opportunity_line_item_price_fill, billing_handoff_contract_activated, campaign_lead_member_enroll, campaign_contact_member_enroll no longer spelled in a runtime file); fixed in 632787a before the final runs. Per-commit evidence: hook-port files run together '31 passed (31) · 746 passed (746)' (VERDICT command-exit 0); flow-port files '515 passed' (earlier session). Static it() sites base 2462 → head 2375 (the 7 deleted self-proofs held 131). No ablation in this card (no new gate); the lint-lowering replacement for action-sandbox's sweep was measured by ablation earlier (control exit 0, mutant exit 1; commit 49b229a message). CI: in_progress (not awaited, per contract).",
    "mcp_calls": "0 — none (GitHub reads via gh api REST only)",
    "api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot] (each one POST /repos/objectstack-ai/objectstack/dispatches): ① pr_create → POST /repos/objectstack-ai/hotcrm/pulls (#2013, draft; read-back 23560 bytes sent = stored); ② label-write --assign os-zhuang → POST /repos//issues/2013/assignees (read-back matches: assignees os-zhuang, labels untouched documentation/ci/cd/dependencies applied by the labeler); ③ post-stamped os-dev-report → POST /repos//issues/1595/comments. git push ×6 (not REST, no force).",
    "open_questions": [
    {
    "question": "test/helpers/ ends with 11 files, not the card's four: verify-stack.ts (the 8 gap paths) plus six static readers (composed-stack 108 importers, src-roster, config-globs, flow-regions, registration-lists, identity-objects) that never executed app behaviour and were not among the five stand-ins. Accept, or chase the 'only four' line?",
    "options": [
    "A: accept as is; the six are static metadata/source readers, out of this card's 'execution' scope, and verify-stack.ts shrinks as the platform closes each gap",
    "B: open a follow-up to fold composed-stack/src-roster reads onto the handle's metadata registry (boots in ~108 more suites; slower) and to retire identity-objects with the remaining hand-built ObjectKernel suites"
    ],
    "recommendation": "A, because none of the six fakes the engine (the card's governing rule 1 is about re-implemented execution), and B costs a boot in ~108 static suites for no fact gained; the hand-built ObjectQL/ObjectKernel suites (~50, e.g. ownership-model's and case-assignment's middleware seam blocks, freeze-guard's cascade block) are a separate, larger migration."
    },
    {
    "question": "AGENTS.md lines 133/138 cited the deleted hook-query-predicate / action-sandbox suites; this PR repointed both (1f952f9). Keep that in this PR?",
    "options": [
    "A: keep — the SSOT would otherwise cite files that no longer exist",
    "B: drop the commit and let a docs card carry it"
    ],
    "recommendation": "A, because a stale citation in the instruction file sends the next agent to a missing file; the edit is two clauses."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public door — sales_rep PATCH /api/v1/data/crm_quote/:id {status:accepted} → 200, crm_contract rows 0, deal closed_won; logged 'quote_on_accepted: could not draft the contract … You do not have permission' · F1 rep quote acceptance drafts no contract (hook writes as caller; sales_rep crm_contract.allowCreate false) — member of the F15 family below · dedupe: quote accepted no contract rep; quote_on_accepted permission; contract draft sales rep",
    "class: a · reach: public door — sales_rep POST /api/v1/automation/quote_generation/trigger + resume on a 200k deal pending opportunity_approval_on_create → FLOW_FAILED 400 RECORD_LOCKED at update_opportunity, the quote is LEFT BEHIND (1 row), stage unchanged · F2 · dedupe: quote_generation record locked orphan quote; approval locked deal quote left",
    "class: a · reach: public door — sales_rep lead_conversion trigger + resume into a NEW account → FLOW_FAILED create_record(crm_account): '[Security] Field write denied: not permitted to edit [annual_revenue]' (sales-rep.profile.ts:100 editable:false; lead-conversion.flow.ts:382 writes it) · F3 · dedupe: lead conversion rep annual_revenue denied; convert lead new account sales rep",
    "class: c · reach: named producer — the app's own seed (celdaysFromNow(..) date values) replayed by @objectstack/verify's boot: ~478 insert WARNs per boot ('must be a valid datetime (ISO-8601)'; campaign/case/event/opportunity/lead/account seeds; the two in_progress seed campaigns refused by campaign_date_validation) · F4 (F5 merged) · reach on objectstack serve unmeasured · dedupe: seed cel date verify boot; SeedLoader valid datetime ISO-8601; seed campaign in_progress dates",
    "class: b · reach: exception security (user ids + org id leave to the external billing endpoint) + public door (a rep's PATCH closed_won) · F6 Seam: spec/descriptor get_record.config.fields ('only these fields are read') → runtime: service-automation get_record → data.find({fields}); measured sys_http_delivery payload_json.line_items keys add organization_id, created_by, updated_by, owner_id, owning_business_unit_id, created_at, updated_at, crm_opportunity · dedupe: get_record fields projection system columns; billing handoff payload owner_id",
    "class: a · reach: public doors — REST insert account {name: 42} + lead {company: 99} (non-string names fold the match keys to NULL) + lead_conversion trigger/resume as sales_manager → the lead converts onto an UNRELATED key-less account (sqlite and memory) · F7 Seam: hotcrm lead-conversion.flow.ts get_record {name_normalized: '{leadRecord.company_normalized}'} → runtime: get_record resolves a null token to '= NULL' (the old harness refused an undefined token); exception: data mixing across customers · pinned as measured defect in account-name-normalized-match · dedupe: lead conversion null company_normalized; get_record null token filter",
    "class: a · reach: public door — admin DELETE /api/v1/data/crm_account/:id on a customer whose contracts are all draft/expired/terminated → DELETE_RESTRICTED dependentObject crm_contract ('This Contact is still referenced by 4 Contract record(s) through Primary Contact') — the cascade deletes contacts before contracts; intent (#549 comment) says they go with the account · F8 · dedupe: account delete cascade contract contact required; master-detail cascade order",
    "class: a (candidate) · reach: the anonymous form door's own execution context ({publicFormGrant, permissions:['guest_portal'], anonymous}, @objectstack/rest registerFormEndpoints) reproduced on the engine; the HTTP route itself is not served by the handle (ENDPOINT_NOT_FOUND), so POST /api/v1/forms/contact-us/submit on objectstack serve is the one read left · F9 web-to-lead and web-to-case submissions are never round-robined (pool read denied → stand-down; measured with an agent staffed: case lands unowned, pinned in case-assignment), web leads alert nobody (lead_assignment notifies {record.owner_id}=null), and lead_duplicate_check never flags a web duplicate (guest lookup denied; the feature's own acceptance case) · dedupe: web-to-lead ownerless no alert; guest_portal pool read denied round robin; web form duplicate never flagged",
    "class: a · reach: public door — sales_rep POST /api/v1/data/crm_opportunity {…, stage_entry_date:'2020-01-01'} → 201, stored stage_entry_date NULL and days_in_stage NULL (absent/null → today/0) · F10 Seam: runtime objectql INSERT shows caller-supplied readonly keys to beforeInsert hooks then strips them (UPDATE withholds them first: readonlyHiddenFromHooks) → app opportunity.hook.ts:352 if (!input.stage_entry_date) stands down · not pinned · dedupe: readonly field visible to beforeInsert hook then stripped; stage_entry_date null on insert",
    "class: b · reach: public door — a sales rep who cannot read a Settlement-Only account (rows as them → []) inserts crm_opportunity on its id → STORED · F11 Seam: REQ-0003 acceptance 2 + the gate's own text ('a denied read … not caught … would make the gate silently absent') → runtime: hook ctx.api findOne under caller RLS returns null (never throws) → app opportunity.hook.ts:540-544 fail-open branch; the engine's reference check accepts the unreadable id · pinned as measured defect in opportunity-account-capability-gate · dedupe: capability gate unreadable account; settlement_only bypass RLS",
    "class: a · reach: public doors, measured via hooks.run (= REST data door) · F12/F13/F15 FAMILY — 'a hook writes cross-object derived data through ctx.api as the triggering person, who holds no edit right on the target; onError log swallows the refusal'. Members: F12 article_feedback_metrics_refresh (admin DELETE of a vote → counters stay; readonly counters dropped for non-system callers); F13 campaign_member_optout_sync (marketing_user unsubscribes a CONTACT member → contact never opted out: 'update on crm_contact requires edit access to its master record'); F15a case_status_side_effects (service agent resolves → account last_activity_date never bumped); F15b campaign_attribution_refresh (sales rep — and a sales manager on a marketing-owned campaign — wins an attributed deal → campaign revenue stays 0); F15c campaign_lead_conversion_refresh (rep conversion → memberships never promoted); F1 above is the same shape. Seam: objectql buildHookApi → ScopedContext(execCtx): hooks assume rights their callers lack. Each member pinned as a measured defect where the base asserted the effect · route to ONE family close-out card · dedupe: hook ctx.api caller permission derived write swallowed; onError log permission denied hook",
    "class: a · reach: public doors — service agent PATCH /api/v1/data/crm_case/:id {status:'escalated'} → 200 and the escalate_case screen action (flows.run + resume) → escalated, owner stays the agent while a service_manager is staffed; rows('sys_user_position') as the agent → PERMISSION_DENIED · F14 read-side twin of the family: case_escalation_reassign (_case-assignment.ts:338) reads the pool as the caller; case-actions.flow.ts:66-75 keeps the status write user-context so the hand-off 'fires from the ACTING USER's write'; only system escalations (SLA sweep) reach it · pinned in demo-staffing · dedupe: escalation reassign service_manager pool permission denied; escalate_case owner unchanged",
    "class: a · reach: public door — any lead conversion on the default (SQL) datasource: admin/system flip of crm_lead.is_converted → campaign num_converted_leads recomputed but every membership stays responded/sent · F16 Seam: app lead.campaign-metrics.hook.ts:73-83 projects fields ['crm_campaign','status'] then updates by m.id → runtime: driver-sqlite-wasm returns only projected columns (no id), driver-memory returns id regardless (measured keys) — the hook is the ONLY writer of member status 'converted' · pinned in hooks-runtime-service · dedupe: campaign member converted never written sqlite; fields projection id missing sqlite",
    "class: c (candidate) · reach: POST /automation + flows.run measured · an unwrapped LOWER({x}) in a flow field template lands verbatim as the literal text 'LOWER(ACME Corp)' while braced forms refuse — an author's function call outside braces silently writes text · dedupe: flow template function outside braces literal",
    "carrier: #1919 (stale comments) · noted, not filed — src comments citing deleted suites (_hook-api.ts:55,158; account.object.ts:271; lead.hook.ts:199; case.hook.ts:75; campaign.hook.ts:35; _line-item-price-fill.ts:31; activity-actions.ts:118,122; _territory.ts:35; account.hook.ts:106,170; lead.campaign-metrics.hook.ts:24; opportunity.campaign-metrics.hook.ts:26), objectstack.composition.ts:339-340, and billing-handoff-closed-won.flow.ts:70 / contract-activated.flow.ts:70 / opportunity-won-alert.flow.ts:70 claiming a bulk updateMany binds previous null (17.7.0 binds the per-row pre-image)",
    "carrier: 承接者:无 · noted, not filed (Acceptance notes) — freeze guards on a SPARSE datasource treat a key absent from the stored row, echoed as null, as an attempted edit (SQL: no change), reach through the record form unmeasured; adding a user to service_agent does not re-grant the triage rule on existing unowned cases until a reconcile; flow-case-actions / unassigned-case-triage-reach drive sharingRules.evaluateRule as a kernel service although POST /sharing/rules/:id/evaluate works on the handle",
    "carrier: PM seat (platform gaps to file on objectstack citing hotcrm#1595) · the 8 local paths kept in test/helpers/verify-stack.ts: CAPABILITY_PROVIDERS not exported (extraPlugins list); systemUpdate (no system UPDATE door); predicateUpdate (no multi:true door); guestInsert (/forms/:slug/submit not served by the handle); runRecordFlow (no user-less / vanished-record trigger door); recordEngineWrites (no observation of writes a hook handed over / async completion / staged refusal); runShippedHook (no lowered-body door); conditionHolds (automation.evaluateCondition)",
    "carrier: #2000 (claude/issue-2000-picklist-metadata @ c8f331c, not touched) · 7 overlapping test files (global-actions, hook-org-inheritance, i18n-references, opportunity-line-item-add-picker, placeholder-picklist-options, quote-accepted-payment-terms, view-references); #2000's versions still import a deleted stand-in — whichever lands second rebases, and if #2013 lands first #2000 must move its additions onto the verify handle"
    ]
    }

  7. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 1595,
    "status": "done",
    "branch": "claude/issue-1595-verify-handle",
    "pr": "#2013",
    "session": "session_012zh91QzFgePbkmuHnugLN3 — this run's harness-stamped id (subagent of the PM seat repo:hotcrm, round R74; rework round on the seat review of 632787a). Addendum to os-dev-report comment 6060172268.",
    "premise_still_valid": true,
    "summary": "Rework round: one fast-forward commit, 632787a → 70084d9, tests only, no src/** change. Item 1: the two flagged cases in test/hooks-runtime-sales.test.ts are now pinned in the measured-defect form (title, comment, and a 'the defect is fixed: rewrite this case' message): ':623 ⚠️ a customer account whose deals are all closed still cannot be deleted (measured defect)' (new finding F17) and ':679 ⚠️ an account whose contracts are all draft, expired or terminated still cannot be deleted (measured defect)' (F8). The sweep then retitled every kept-title case whose asserted outcome flipped: 20 head titles from 19 base cases, listed in the PR body as old → new → file:line. One more is a defect of the same shape, pinned as ':920 ⚠️ a contact whose only contract is expired still cannot be deleted (measured defect)' (new finding F18). Titles in the ⚠️ … (measured defect) form: 10 at 632787a, 13 at 70084d9. Item 2: the PR body gains F17/F18 in the defect list and a 'Retitle round' section (the table, the sweep method, the cases checked and left as is, the verify reading); nothing else in it changed. Deviations, all inside item 1's intent: (a) 'allows deleting a contact whose references are all settled' held a true half and a defect half, so it is split in two with no matcher change; (b) the whitespace-only fold row moves from 'folds %s' to its own it.each table ('refuses %s before any fold — the name/company is required'), with no matcher change; (c) the doc comment of quote-accepted-lookups.test.ts:253 promised 'the absent lookup absent', which the body no longer asserts, and is corrected to match the new title; (d) the three defect pins' failure-message strings now carry the rewrite-me text. Two cases where only a half was dropped (no flip) are reported, not retitled: global-actions.test.ts:483 and script-bodies.test.ts:437 'reads sys_user once — and only because the dispatcher delivered no name'. The delivered-name half is unassertable because no door delivers a name on 17.7.0.",
    "tests": "pnpm verify on 70084d9 (git rev-parse --short HEAD printed by the run itself: '70084d9c'), under OS_VERIFY_LOCK_SLOT=hotcrm-1595 os-verify-lock.sh: 'os-verify-lock: VERDICT command-exit 0 · held the lock 524s (8m44s)'. Stages: validate '✓ Validation passed'; typecheck (tsc --noEmit) clean; lint (objectstack lint --strict) '18 suggestion(s)', 0 errors 0 warnings; lint:i18n-gate '✓ i18n lint gate: 0 i18n/missing-* issues'; hygiene '✓ source hygiene clean'; hygiene:tokens '✓ source token ratchet clean'; build '✓ Build complete', the same 8 author-time approval-approvers-may-resolve-empty warnings; test 'Test Files 172 passed (172) · Tests 3564 passed | 1 skipped (3565)' (+1 is the split contact case). Before the commit, the eight edited files ran under the lock: 'Test Files 8 passed (8) · Tests 244 passed (244)', VERDICT command-exit 0, held 71s; the new titles render as written. Coverage was not re-run: no matcher changed, and the seat said it is not needed. F17/F18 evidence came from two temporary probe tests, never committed and deleted after use; git status was clean before the commit. Sweep method: every it/test call was parsed with the TypeScript compiler API, base (all 178 test files at 99d290a, deleted suites included) against head. Of 2437 head cases, 2365 keep a base title verbatim: 1723 have an identical body, 642 a changed one. Every changed pair was screened, and every pair that screened as a possible flip was read side by side: 68 flagged by polarity signals (a refusal captured where success was asserted or the reverse, a newly asserted error code, a positive/negative matcher shift, new refused/unreachable/measured wording), plus 246 whose multiset of matchers and literal arguments changed. The other 328 had identical matcher and literal-argument multisets (setup-only changes). No ablation in this round: no gate is new and no matcher changed. CI: in_progress, not awaited per contract.",
    "mcp_calls": "0 — none (GitHub reads via gh api REST only)",
    "api_writes": "2 this round, both through the fleet-write relay as objectstack-fleet[bot] (POST /repos/objectstack-ai/objectstack/dispatches): ① post-stamped --body=2013 → issue_patch PATCH /repos//issues/2013 (relay run 37782702965; read-back 31520 bytes sent = stored). The first attempt was REFUSED by the tool's own unread-comment pre-check, so nothing was written. The one newer comment, 6060103415 from vercel[bot], is a 'Skipped Deployment' status with no request to receipt; after reading it, the write was re-run with --ack-through=6060103415. ② post-stamped --comment=1595 → POST /repos//issues/1595/comments (this addendum). git push ×1, a fast-forward 632787a..70084d9 (not REST, no force). Round one's 3 relay writes are unchanged (comment 6060172268).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door — admin DELETE /api/v1/data/crm_account/:id on a customer with one open deal → 409 userMessage 'Cannot delete customer account: 1 open opportunity still references it. Close or reassign it first.'; PATCH /api/v1/data/crm_opportunity/:id {stage: closed_lost, loss_reason: competitor} → 200; the same DELETE → 409 DELETE_RESTRICTED, dependentObject crm_opportunity, dependentCount 1, 'This Account is still referenced by 1 Opportunity record(s) through “Account”, which is required and cannot be cleared. Delete or reassign them first.'; control: deal deleted → account DELETE 200, row gone · F17 (F8 family, new) closing the deal the guard names does not unblock the delete · Seam: app account.hook.ts:202-222 (counts stage $nin closed_won/closed_lost; tells the user 'Close or reassign') → runtime: objectql restrict on the required, non-cascading lookup crm_opportunity.crm_account (opportunity.object.ts:64-69) · pinned hooks-runtime-sales.test.ts:623 · dedupe: account delete closed opportunity restricted; close or reassign misleading account delete; crm_opportunity crm_account required restrict",
    "class: a · reach: public door — admin DELETE /api/v1/data/crm_contact/:id under an activated contract → 409 'Contact Ada Lovelace is still referenced by 0 open opportunity(ies), 0 active quote(s), 1 active contract(s), so it cannot be deleted — and neither can its account, because deleting an account deletes its contacts. Close or reassign those records first.'; PATCH /api/v1/data/crm_contract/:id {status: terminated} → 200; the same DELETE → 409 DELETE_RESTRICTED, dependentObject crm_contract ('This Contact is still referenced by 1 Contract record(s) through “Primary Contact”, which is required and cannot be cleared. Delete or reassign them first.'); after a system write to expired → the same 409; control: contract deleted → contact DELETE 200 · F18 (F8 family, new) · Seam: app contact.hook.ts:160-170 (counts active contracts only; 'Close or reassign') → runtime: restrict on the required, non-cascading lookup crm_contract.crm_contact · pinned hooks-runtime-sales.test.ts:920 · dedupe: contact delete expired contract restricted; contact_integrity close or reassign misleading; crm_contract crm_contact required restrict",
    "carrier: family close-out card (seat to file, per the pm-dispatch contract) · F8 + F17 + F18 are one family: deletion intent (the guards' 'Close or reassign' / 'Terminate or reassign' text and the account→contact cascade) against the engine's referential rule on required lookups. Route all three to one close-out card, not three single-point cards",
    "carrier: 承接者:无 · noted, not filed — a dropped half, not a flip: global-actions.test.ts:483 and script-bodies.test.ts:437 'reads sys_user once — and only because the dispatcher delivered no name' no longer assert the delivered-name half (no door delivers a name on 17.7.0). Title left as is; the seat may ask for a reword"
    ]
    }

  8. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Verdict: os-dev reports 6060172268 (round 0) and 6060702334 (round 1, rework), PR #2013 — ACCEPT, governed (Tier H): the PR stays draft for the maintainer. repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T13:37Z.

    Claim amendment (file surface) · claim 6055790691. The diff left the declared surface in four places. Each is accepted as part of this card:

    • AGENTS.md (2 citations) and docs/developers/code_examples.md (1 citation): they named suites this PR deletes. This change made them false, so they are fixed here.
    • New test/helpers/verify-stack.ts: the shared boot options plus the one-local-path-per-gap the card's rule 5 allows.
    • New test/script-bodies.test.ts: the business facts action-sandbox.test.ts carried, moved.
    • Deleted test/sharing-posture-declaration.test.ts: a guard over hand-built kernels mounting SharingServicePlugin without a tenancy probe. On the head no suite mounts that plugin by hand (git grep finds one doc-comment mention), so the guard had no subject.

    Checked on GitHub and in git, not taken from the reports:

    What the port surfaced, filed by the seat (the card's sweep output, grouped):

    Code scanning, optional: CodeQL review comment 4219063113 (alert 385, "Unused variable") flags nodeStatus at test/flow-record-change.test.ts:549, a dead local helper. The CodeQL check itself is success. It is not a push trigger: a push now would reset the contract-reviewed head and any approval. It rides the next push that already edits this file (#2014 or #1919).

    The review's ③ escalations, answered by the seat:

    The dev's open questions: both were answered A in the review message. Q1 accepts the six static helpers as they are. Q2 keeps the AGENTS.md repoint; the maintainer confirms it by approving.

    Governed landing, four parts:

    1. This record.
    2. PR test: run the hook, flow and action suites on @objectstack/verify and retire the five hand-built harnesses #2013 stays draft, labelled needs-user-decision, with the final 「维护者速读」 posted as a PR comment.
    3. Review requested from os-zhuang and hotlong.
    4. Listed in the round report as awaiting a human merge.

    On an authorized approval, the seat lands the PR through the merge queue; a direct merge by the maintainer is its own record. This card stays pm:dispatched until MERGED.

    Serial consequences:

    Implemented-by: os-dev subagent of this session (branch claude/issue-1595-verify-handle) · Reviewed-by: repo:hotcrm seat (contract review: isolated at-tier reviewer, adopted)


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Landed: PR #2013 MERGED as 9451b6de on main (squash, a single parent 99d290ad), through the merge queue. The maintainer approved (review 5457605423, on head 70084d9c), then made the PR ready and queued it by hand. That direct act is its own record. repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T13:49Z.

    Two readings:

    • the PR object reads merged: true, merged by os-zhuang at 13:48:53Z;
    • origin/main is 9451b6de test: run the hook, flow and action suites on @objectstack/verify ….

    Closed as it should be: Fixes #1595 closed this card. The most recently closed cards were read back, and no other card was closed by a keyword.

    This act: removes pm:dispatched and the assignee here, and needs-user-decision from PR #2013.

    Follow-ups now unblocked, in order:

    1. Hooks that write or read across objects as the triggering person fail silently for reps, agents and web forms — 11 hook sites, one fix pattern (runAs), measured on the real engine by #2013 #2014 (p2), which flips this PR's 13 measured-defect pins.
    2. [finding] 26 source comments in src/ cite src/flows/ — a directory ADR-0130 removed (blocked on Track A: file surface collides) #1919 (p3), stale comments: now also the 24 lines citing the deleted suites, _hook-api.ts:55 and :158 first.
    3. On resume, feat(picklists): the shared option lists become picklist metadata — *.picklist.ts and Field.select({ picklist }) replace _picklists.ts (acceptance of objectstack#18164) #2000 moves its 7 overlapping test files onto the verify handle (noted there, 6061167800).

    Carried nit: CodeQL's unused nodeStatus (test/flow-record-change.test.ts:549) rides the next push that edits that file.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions