Repository navigation
objectql: a master-detail row whose master is missing can be neither read nor removed through any door — the admin answers 404 by id, queries omit it, and the integrity audit only reports it #24964
Description
Filing gate ① a measured defect, found by a repo:hotcrm seat 2 dispatch for objectstack-ai/hotcrm#2073 item 6 (report 6298567112 on that card; the HotCRM PR objectstack-ai/hotcrm#2211). It was measured on HotCRM main b9ecab6d with @objectstack/* 18.0.0, on the @objectstack/verify boot (SQL driver). Filed by the seat (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.
Reader (who acts): objectstack triage routes it. HotCRM cannot fix this app-side: removing the rows needs a door the platform does not offer (hotcrm AGENTS.md §3: platform tooling lives in the platform).
The finding, as measured
- How such rows exist:
- Until [Decision] Sales reps and sales managers can enroll campaign members although their profiles grant no create and say "Enrollment belongs to marketing_user": enforce the profile (A) or grant it (B)? hotcrm#2167, HotCRM's Add to Campaign actions were script bodies, which run elevated.
- A system write is exempt from the write-time reference check, so a typed campaign name was stored as
crm_campaign_member.crm_campaign, naming no campaign. crm_campaign_memberis a required master-detail child ofcrm_campaign(controlled by parent).- Every released HotCRM through 5.1.2 carries that body. The 2026-10-12 hosted rig holds one such row.
- What an administrator can do with one today:
GET /api/v1/data/crm_campaign_member/<id>→404 RECORD_NOT_FOUND;DELETE→404;- a list query leaves the row out.
- The row's access derives from a master that does not exist, so every user, the admin included, is refused.
- What it costs:
- The campaign hooks that meet it (
campaign_member_metrics_refresh,campaign_lead_conversion_refresh) logRecord … not found in crm_campaignon every later write that touches it. - One measured business effect: when an orphan membership sorts before a real, unsubscribed one, a sales rep's lead conversion answers ok, but the real campaign's
num_converted_leadsstays 0 (expected 1). The recompute loop throws at the missing campaign (onError: log) and never reaches the next one. Ordered the other way, it reads 1.
- The campaign hooks that meet it (
- Reach: any install that ever ran a system-context writer which stored a reference to a missing master. Each such row stays until a database edit.
Seat review (source at 18.0.0, 06cab4849)
packages/objectql/src/engine.ts:9720assertReferencesResolvereturns early for anisSystemcaller, by design (:9855). So such rows can be written.inspectDanglingReferences(engine.ts:10778;lifecycle-service.ts:118) finds them.- The lifecycle sweep's audit says so and stops: "stored references that resolve to nothing — reported, never rewritten" (
packages/objectql/src/integrity/dangling-reference-audit.ts:736). - No HTTP route and no CLI command lets an operator act on what the audit finds. The audit names the row; nothing can remove it.
- Candidate shapes, for triage:
- an operator-only removal door (CLI, or an admin-only route) over
inspectDanglingReferences' findings, dry-run by default; - or a lifecycle remedy that applies the master-detail's own delete semantics to a detail whose master is gone.
- Either way the app keeps no tolerance for the dangling reference: HotCRM's hooks stay fail-loud (docs(datasource): external-datasource federation guide + objectstack-data skill rule (ADR-0062) #2211 adds none).
- an operator-only removal door (CLI, or an admin-only route) over
Dedupe
Method: every objectstack-ai/objectstack issue title (every state, #1–#24950), listed through the repo-scoped issues?state=all endpoint (refreshed with since=2026-10-20T17:00Z), matched case-insensitively on dangling|orphan|inspectDangling|missing master|resolve to nothing|unresolv… reference|isSystem … reference. The search API is refused for this session.
- Related, closed: [finding] master-detail parent binding reads the header with no tenant —
parent.*predicates leak another org's header fields; the dangling-reference audit is blind to cross-org references #19837 (master-detail parent binding reads the header with no tenant, and the dangling-reference audit is blind to cross-org references). That card is about the audit's reach, not a remedy door. - Different: finding(cli):
os secret orphans, a report that "writes nothing", composes the settings service with its default crypto provider, which mints a key file in the key home in a development posture with no key #21471, An unprojected REST data query returns ORPHANED columns that no metadata declares (fields retired in an upgrade), outside any field-level rule, untilos migrate apply --allow-destructive#21571, migrate: a sanctioned, operator-only read of unmapped (orphaned) columns for data conversion, before--allow-destructivedrops them (the coupling #21571 names) #21573 (orphaned columns and secrets); metadata-protocol: an adopt-orphans into a package rebinds a runtime object's row in storage, but the registry keeps the sys_metadata owner and the old definition until a restart (ADR-0070 D5) #23646, metadata-protocol: an orphan adoption registers a package-less DRAFT view as live — reassignOrphanedMetadata's view write-through has no state gate, so the registry serves a draft the boot never registers (found by #23646) #23690, packages: POST /packages/:id/adopt-orphans skips requireWritablePackage, so it can bind loose items into a read-only package #24813 (orphaned metadata adoption). - New.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 20, 2026
Decision-box analysis ·
domain:engineseat 2 (seat post #20966) ·session_01QK6pdbxKZiHdFApL8RYVh9· 2026-10-20T23:05Z. State:pm:queue→needs-user-decisionin the same act. ⛔ Not a claim.一句话问题
一条明细记录(活动成员)指向一个已不存在的主记录(活动)时,任何人——包括管理员——都读不到、删不掉它,它会一直拖累依赖它的统计(实测:一位销售转化线索后,真实活动的「已转化线索数」停在 0)。平台的完整性审计能指出这条记录,却没有任何一扇门让人处理它。要裁的是:平台用什么载体让人把这类记录清掉。
背景
packages/objectql/src/integrity/dangling-reference-audit.ts头部「## Reports; never rewrites」:悬空引用的处理(补回主记录,或清掉引用)是"审计做不了的判断",审计零写入,报告交给人。——人拿到报告后无门可走,正是本卡。user:password@hostin driverconfig.url) remain a live cleartext door after #7990 — refuse at publish, or accept as residual risk? #8082 同一模板;2026-08-26 再裁 B,5421208707):对孤儿行的破坏性清理,载体是运维显式命令,默认只报告,删除需显式开关,删除前强制导出;⛔ 不做启动/升级时的自动迁移。前提(每条带 re-check 命令,复升级时逐条跑)
git grep -n "if (context?.isSystem) return;" origin/main -- packages/objectql/src/engine.ts(对照词:同文件inspectDanglingReferences)。2026-10-20 读数:engine.ts:9727,assertReferencesResolve内。git grep -n "Reports; never rewrites" origin/main -- packages/objectql/src/integrity/dangling-reference-audit.ts(对照词:inspectStrandedRequests)。读数::30。git grep -n "danglingReferences" origin/main -- packages/cli/src packages/rest/src packages/runtime/src(对照词:同命令改搜packages/objectql/src必中lifecycle-service.ts)。读数:cli/rest/runtime 零命中,objectql 两处(报告字段与赋值)。b9ecab6d,18.0.0),本席未复测。选项 × 真实代价 × 工作量
os命令:默认 dry-run,列出"主记录已不存在的 master-detail 明细"(对象/记录/字段/缺失的主 id);--apply才删,删前强制导出;删除按该 master-detail 自身的删除语义(级联其下明细);⛔ 任何启动/升级路径都不调用它domain:cli+domain:engine(删除原语)· 1 PR · 先例os secret orphans(#8103),其落地行数未测(本地为浅历史)domain:services(plugin-security)+ engine · 1–2 PR · 无先例业务含义直译
四轴(业务立场)
os secret orphans)同形,不新增模式;B 与审计头部「Reports; never rewrites」和母单的"⛔ 不做自动迁移"正面冲突;C 在权限派生里开一个特例,长期每个读写门都要记得它;D 把平台该有的工具推给应用方,违背"平台相关的功能,应该尽量先改平台"。os-decision-facets
① 长远合理性:A 复用已裁模板、不增特例;B 与已裁"不自动迁移"冲突;C 扩大权限特例。
② 业务拉动:HotCRM 托管环境实测 1 条,≤5.1.2 装机均可能有;影响活动统计。
③ 防 AI 犯错:A 删前 dry-run + 导出,响亮;B 静默删除;C 掩盖写入端错误。
④ 创业阶段不扩散:A 新增一条命令但复用模板;C 新增永久权限特例;D 零新增、成本外移。
Prior rulings read: dangling,orphan,master-detail,cascade,integrity → 14 hits; ADR-0055 Decision §3/§4 (write mechanism, not removal), the #8103 ruling 5271021652 / 5421208707 (operator command template); thread: none
推荐:A(置信缺口见下)
裁后执行
pm:queue(带母单五条约束:显式命令、默认 dry-run、显式删除开关、删前强制导出、任何启动/升级路径不调用),engine 席认领;CLI 命令面按跨车道声明给domain:cli。domain:devx文档卡。相关
#8103 / #8082(母单与同模板),#19837(审计覆盖面,已关),objectstack-ai/hotcrm#2073 / objectstack-ai/hotcrm#2211(发现来源;HotCRM 侧 hook 保持响亮失败)。
Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Ruled: A ·
domain:engineseat 2 (seat post #20966) ·session_01QK6pdbxKZiHdFApL8RYVh9· 2026-10-20T23:07Z. State:needs-user-decision→pm:queuein the same act.Provenance: the maintainer, in this seat's session (
session_01QK6pdbxKZiHdFApL8RYVh9), answering the in-session question on this card's analysis 6303771280, verbatim: 「A 运维显式命令 (Recommended)」.Freshness: the thread was re-read before recording; 6303771280 is its only comment, so nothing newer than the analysis was outstanding. No
Blocked-by:line stands on this card.Ruling, as it binds the dispatch (the #8103 template, 5271021652 / 5421208707, inherited with its reasons):
osCLI). ⛔ Nothing on any boot, upgrade, migrate or lifecycle-sweep path invokes it, and it must not grow such a caller.Out of this ruling: a lifecycle auto-remedy (B), any change to the permission derivation (C), and the system-write exemption in
assertReferencesResolve(the audit header keeps it).Execution: back to
pm:queue,domain:engine(the removal primitive inpackages/objectql), with the CLI command face declared cross-lane todomain:cli. The claiming seat citesRuling-ref: this commentin its claim and dispatch.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 2 · 2026-10-21T01:45Z · p2,
domain:engine(triage's grade), ruled A by the maintainer (6303808312). A master-detail row whose required master is missing can be neither read nor removed through any door; the ruling builds an explicit operator command for it on the #8103 template.Session:
session_01QK6pdbxKZiHdFApL8RYVh9(the dispatching seat; the dev is amode:subagentrun of this session, told apart by its branch)Account:
os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24964-orphan-detail-operator-commandWorktree:
objectstack-issue-24964Domain:
domain:engineSeat:
domain:engine#2(seat post #20966)Ruling-ref: 6303808312 (read at this claim; its five binding constraints are the dispatch's ruling section)
File surface:
packages/objectql: the removal of a listed orphan detail row, applying the master-detail's own delete semantics, through the engine's existing system-context doors overinspectDanglingReferences' findings (engine.tsnear:10820;integrity/dangling-reference-audit.ts). The audit itself stays report-only.packages/cli: one newoscommand besideos db clean/os secret orphans(commands/db/orcommands/data/, the dev's measured choice). It is dry-run by default, deletes only behind an explicit flag, writes a mandatory pre-delete export first, and no boot, upgrade, migrate or lifecycle path calls it. This is cross-lane, declared todomain:cli's seat.⛔ Not in scope: a lifecycle auto-remedy (B); any change to the permission derivation (C);
assertReferencesResolve's system-write exemption; dangling plain lookups that are not a required master-detail master (report-only stays, unless the dev measures that the same command reads them, and then it names them in the dry run but never deletes them); HotCRM.Container & model:
M,mode:subagent,model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate"; judgement under a five-constraint ruling)Clause-②: no (a new operator command under the ruled template; no published accept set widens). Whether an engine method the CLI needs becomes a package export is NOT read by the seat: the dev measures it on the built declaration closure and reports, and the claim is re-declared
yesif one is added.Responsibility: HotCRM's pre-5.1.2 elevated scripts wrote the rows; the platform's system-write exemption let them in by design | the dangling-reference audit, which only reports | any operator whose install ran such a writer; the 2026-10-12 hosted HotCRM rig holds one row
Thread-read: 6303808312
Serial constraints cleared: the open PRs' file lists were read at this claim. None touches
dangling-reference-audit.ts,lifecycle-service.ts,packages/cli/src/commands/data/orcommands/db/. Engine regions held elsewhere: #25009 (this seat, the ReadableIds leaf near:6135), PR #24893 and PR #25016 (other regions); this card's region isinspectDanglingReferencesnear:10820.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 24964,
"status": "done",
"branch": "claude/issue-24964-orphan-detail-operator-command",
"pr": "#25039",
"session": "session_01QK6pdbxKZiHdFApL8RYVh9 — mode:subagent run of the dispatching seat's session (the parent's id; told apart by its branch)",
"premise_still_valid": true,
"summary": "New explicit operator command
os db orphans(packages/cli/src/commands/db/orphans.ts, plan in utils/orphan-detail-sweep.ts). Its default run is a read-only report: each detail row whose REQUIRED master_detail master does not exist, with object, record id, field and missing master id, plus the deeper details its delete's cascade removes. Undetermined references, other dangling fields and withheld orphans are reported and never deleted. --delete requires --export: the export holds every record to be deleted as stored, is written 0600 with wx and read back before the first delete. The run re-runs the audit and re-reads each cascade just before deleting, deletes through the engine's own system-context delete(), and judges each delete by reading its records back. ERR_SUMMARY_RECOMPUTE on the missing master's roll-up is noted rather than failed. objectql gains one read-only public method, ObjectQL.inspectCascadeDelete(object, id): the cascade set delete() collects, read through the same walk, filing no elevation record. MEASURED: it reaches the published d.ts closure (dist/util-.d.ts 2 hits; control inspectDanglingReferences 3 hits), so @objectstack/objectql's surface widens by one method, and the claim's Clause-② no is for the seat to re-declare (open_questions). H1 holds (undetermined is a count only). H2 holds, measured on a real kernel and SQL driver: the system delete removes the orphan and its cascade, and a roll-up on the missing master throws SummaryRecomputeError(written) after removal. H3 holds (secret orphans shape and helpers reused). Ruling constraint 5, declared in the PR: the dry run lists every record any cascade relation reaches. restrict and set_null are not listed, because they remove nothing. A restrict dependent refuses the delete atomically (pinned). set_null clearing is the engine's existing behaviour, NOT MEASURED here.","tests": "HEAD 0770e4f. objectql targeted: vitest run --project local --maxWorkers=2 engine-cascade engine-inspect-cascade-delete engine-dangling-reference integrity/dangling-reference lifecycle-service: Test Files 18 passed (18), Tests 328 passed (328), exit 0. cli unit tier, whole: Test Files 318 passed (318), Tests 4699 passed (4699), exit 0. cli integration targeted: orphans.integration.test.ts Tests 9 passed (9), exit 0. One-shot family pin at 26706a6 with -t 'db orphans|the bootSchemaStack family is the table above|arms no lifecycle sweep': 10 passed, 98 skipped, exit 0. Unit: orphan-detail-sweep.test.ts 6 passed and orphans.no-auto-run.test.ts 7 passed. typecheck: pnpm --filter @objectstack/cli typecheck and @objectstack/objectql typecheck both exit 0, check:test-typecheck OK with no new debt. Ablation through scripts/ablation-replace.mjs, CLI src imported directly (no dist in the path), each leg's marker grep -c 1 during the run, each restore proven blob==HEAD with git diff HEAD empty. (a) master-detail filter -> return true: integration 5 failed / 4 passed, unit 1 failed / 5 passed. (b) export not written before deletes (verified from memory): integration 3 failed / 6 passed; the order pin read 'no file' at the first delete. H2 measurement (scratch test, deleted before commit): inspectDanglingReferences returned the orphan in dangling; system delete removed it and its note; SummaryRecomputeError ERR_SUMMARY_RECOMPUTE written=true, 'Record Spring Promo not found in oc_campaign'. JSON purity, single-member spawn (tsx bin/run-dev.js db orphans --json, no database): exit 0, one JSON document on stdout, boot diagnostics on stderr, no database file created.",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 0770e4f derived 105 commands, each run with its exit code captured to a file before any pipe: 105/105 exit 0. --ran reconciliation: 'Run reconciliation — 105 derived, 105 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. The first run at 43f2159 had one real red: check:slot-lookup, NEW service-lookup erasure in orphans.integration.test.ts, fixed in 0770e4f. It also had four prerequisite refusals: engine-split-ratio (shallow, so history was deepened with --shallow-since=2026-07-16), check:skill-examples, check:dual-build-cjs-loads and check:i18n-coverage (missing builds of out-of-diff packages). All four are green at 0770e4f. CI check-runs at head 0770e4f, read once: No other open PR may claim the same issue = success. Packed-tarball smoke and 18.x upgrade harness = skipped. Type Check (workspace, consumer gates, debt ledger, source gates), Lint (PM dispatch-gates, ESLint, tooling self-tests, CI/release/engine, slot lookup & corpus, docs & code guards, repo & tooling, query-options erasure), Check Changeset, Governed Surface Queue Guard, The card this PR closes must claim this branch, Part-of PR must not also close its card, Check PR Size, Spec property liveness, Check Documentation Links and Flag docs affected = in_progress. Test Core and Build Core were not yet listed.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 relay strokes (scripts/pm/with-fleet.sh --via dispatch, executed as objectstack-fleet[bot]). Each is sent as POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/25039/assignees. Stroke 2, comment: POST /repos//issues/24964/comments (this report). Plus git push (not REST). Reads: single-card REST GETs of issue 24964, its comments, pulls/25039 and its check-runs.",
"files_changed": [
".changeset/24964-orphan-detail-operator-command.md",
"content/docs/deployment/cli.mdx",
"packages/cli/src/commands/db/orphans.ts",
"packages/cli/src/commands/db/orphans.integration.test.ts",
"packages/cli/src/commands/db/orphans.no-auto-run.test.ts",
"packages/cli/src/utils/orphan-detail-sweep.ts",
"packages/cli/src/utils/orphan-detail-sweep.test.ts",
"packages/cli/src/utils/schema-migrate.one-shot-family.integration.test.ts",
"packages/cli/test/json-stdout-purity.e2e.test.ts",
"packages/objectql/src/engine.ts",
"packages/objectql/src/engine-inspect-cascade-delete.test.ts"
],
"line_budget": "n/a — no skills/** file and no line-ratcheted ledger touched. Diff +1788/-1 over 11 files, under the 3000-line human-merge threshold.",
"deviations": [
"Declared narrowings: objectql tests ran targeted by file name (cascade, dangling-reference and lifecycle families: 18 files), not the whole 453-file suite. The cli integration tier ran only the files this diff touches. The one-shot family pin ran -t-selected rows at 26706a6. test/json-stdout-purity.e2e.test.ts is declared to CI, with a single-member spawn measured locally instead. CI Test Core runs all of it.",
"origin/main gained 5 commits after the merge at 43f2159, up to 3b3830c. Their only overlap is packages/cli/test/hook-timeout-override-refusal.test.ts, which this diff does not touch, so they were not merged again before the PR. The CI merge ref covers them.",
"The changeset carries no Clause-② line. The PR body carries the claim's line verbatim, as the role file requires, but the measured new public method makes 'no' understate. One home is left for the seat's re-declaration, rather than publishing 'no' in CHANGELOG text. Both packages are graded minor: the WHICH LEVEL rule for an additive published-surface widening, and the repo's convention for a new command.",
"Attribution followed AGENTS.md over the harness reminder. Commits carry the model-free trailer pair (Claude-Session plus Co-authored-by: Claude), and the PR body uses the session-URL footer form.",
"History was deepened (git fetch --shallow-since=2026-07-16 origin main) to measure check-engine-split-ratio. The fetch was additive and touched the shared object store only."
],
"open_questions": [
{
"question": "Clause-② on PR 25039: the claim declared 'no', and this PR adds one public method, ObjectQL.inspectCascadeDelete, measured in @objectstack/objectql's published dist/util-.d.ts. Repo precedent for a new CLI command is also 'Clause-②: yes (widening)' (cc305a3 os migrate organization-ownership; os migrate unmapped-columns). Should the seat re-declare?",
"options": [
"A: re-declare 'Clause-②: yes (widening)' in the PR body, and add the same line to .changeset/24964-orphan-detail-operator-command.md. Both packages are already minor, so the level axis passes.",
"B: keep 'no'. The axis stands down either way, but the declaration understates a measured surface widening."
],
"recommendation": "A. The widening is measured on the built declaration closure, and the dispatch named this exact re-declaration as the seat's act."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — DanglingReferenceReport.undetermined is a count, not itemised, so os db orphans can say how many references it could not check but not which rows. Itemising it would change the audit's published report shape (PR Acceptance notes).",
"carrier: none · noted, not filed — 'required master-detail' is read through isControlledByParentMasterField (@objectstack/spec/data), whose body is exactly type master_detail plus required true. Its name speaks of controlled_by_parent; the predicate does not depend on the sharing model (PR Acceptance notes).",
"carrier: none · noted, not filed — the HotCRM premise (an administrator's GET/DELETE by id answer 404) was not re-measured. This boot composes no security plugin, and a user-context read there returned the row. The removal door does not depend on it."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{
"issue": 24964,
"status": "done",
"branch": "claude/issue-24964-orphan-detail-operator-command",
"pr": "#25039",
"session": "session_01QK6pdbxKZiHdFApL8RYVh9 — mode:subagent patch round of the dispatching seat's session (the parent's id; told apart by its branch)",
"premise_still_valid": true,
"summary": "Patch round on PR 25039, head 0770e4f verified before the first edit and the newest Claim: (comment 6305964995) names this branch. Item 1: .changeset/24964-orphan-detail-operator-command.md gains the line
Clause-②: yes (widening)on its own line after the summary line, where other changesets on main place it (checked against 24750-invite-role-not-found-code and 24808-user-field-foreign-reference-warning). Levels stay minor for @objectstack/cli and @objectstack/objectql. One commit, 5231f8f, pushed normally (0770e4f..5231f8f, no force, no amend). Item 2: the PR body's Clause-② line (line 3) now readsClause-②: yes (widening) (the new public method ObjectQL.inspectCascadeDelete on @objectstack/objectql, and the new command os db orphans), written by one issue_patch stroke through the relay, which accepted it. Read back: the stored body is byte-identical to the bytes sent, only line 3 differs from the previous body (60 lines before and after), one session-URL footer, and the PR is still draft with the same assignee, labels and head. No code, test or doc changed.","tests": "None run: the diff is two lines of changeset text. The gates below are the evidence.",
"gates": "HEAD 5231f8f, --base origin/main (d68aab6), merge base 4d99158; every exit code was captured to a file before any pipe. (1) node scripts/check-changeset-no-major.mjs --base origin/main --event FILE, the payload built from GET /pulls/25039 AFTER the body edit (stored body, labels, draft): exit 0. Its lines: "This diff introduces no
majorbump", "LEVEL AXIS: this PR declares clause-②yes (widening), and no package whose published source it moves is gradedpatch", "direction arm:widening". The same gate with the proposed body before the write: exit 0, same lines. (2) node scripts/check-adr-0087-registration.mjs --base origin/main: exit 0, "adds no declared-breaking changeset (1 non-breaking changeset(s) seen)". (3) node scripts/check-empty-changeset.mjs --base origin/main: exit 0, "1 declaring changeset(s) added". (4) pnpm check:changeset-gate-self-tests: exit 0 (170 + 548 + 401 assertions). Extra, because they read changeset text, all exit 0: check-issue-citations, check-keyed-text-bounds, check:closed-ceremony-text, check:driver-memory-census, check:nul-bytes (no raw control bytes), check:objectui-changeset, check:pm-changeset-deadline-census, check:published-files. Declared narrowing: the other commands of the 105-command derived list (node scripts/pm/dispatch-gates.mjs --commands, exit 0, still 105 at 5231f8f) were not re-run, because this round's delta is two changeset lines and the previous round ran 105/105 at 0770e4f; a grep of the 76 scripts that list references for the .changeset directory found 14, and every one of them that reads changeset text, bar two release-lane self-tests, was run above. CI check-runs at head 5231f8f, read once and not waited on. success: Check Documentation Links, Spec property liveness, Auto Label, Check PR Size, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch (one of its two runs). skipped: 18.x upgrade harness, Packed-tarball smoke, one Auto Label run and one Check PR Size run. in_progress: Check Changeset (two runs), Governed Surface Queue Guard, Flag docs affected by code changes, Lint (CI, release & engine gates; ESLint; PM dispatch-gates; docs & code guards; repo & tooling gates; query-options erasure; slot lookup & corpus; tooling self-tests), Type Check (consumer gates, debt ledger, source gates, workspace), and the second run of The card this PR closes must claim this branch. Test Core and Build Core were not yet listed.","dogfood_battery": "n/a — no
!in the PR title and the declared arm is (widening), not (narrowing)","mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 relay strokes (scripts/pm/with-fleet.sh --via dispatch, executed as objectstack-fleet[bot]), each sent as POST /repos/objectstack-ai/objectstack/dispatches. Stroke 1, issue_patch: PATCH /repos//issues/25039, body only (run 39538875952, conclusion success; the relay's read-back: 11103 bytes sent, 11103 stored, identical). Stroke 2, comment: POST /repos//issues/24964/comments (this report). Plus one git push (not REST). Reads: single-object REST GETs of pulls/25039 (four), the check-runs of commit 5231f8f, issues/24964/comments and two comments by id.",
"files_changed": [
".changeset/24964-orphan-detail-operator-command.md"
],
"line_budget": "n/a — no skills/** file and no line-ratcheted ledger touched. This round adds 2 lines to one changeset.",
"deviations": [
"The brief limited the PR-body edit to the one Clause-② line, so line 5 of the body was left as written. It now contradicts the line above it (see open_questions).",
"The PR-body line carries its reason as a second parenthetical after the arm, as briefed. The gate reads the arm from the first parenthetical (echoed above: widening). The changeset line carries no reason, as briefed.",
"The branch was not merged with main in this round (changeset text only). The gates diff against the merge base 4d99158, with --base origin/main as briefed.",
"Attribution followed AGENTS.md over the harness reminder: the commit carries the model-free trailer pair (Claude-Session plus Co-authored-by: Claude).",
"A worktree was built at /home/user/objectstack-issue-24964 from the existing local branch (equal to the remote head 0770e4f); pnpm install --frozen-lockfile exit 0. It is removed after this report, without --force."
],
"open_questions": [
{
"question": "PR 25039 body, line 5, now contradicts the line it follows. It reads "The line above is the claim's (comment 6305964995), copied as the dispatch requires. Measured for the seat to re-declare: ... The changeset carries no Clause-② line, so the re-declaration lands in one place." After this round the line above is the seat's re-declaration and the changeset does carry the line. Should that paragraph be replaced or removed?",
"options": [
"A: the seat rewrites it to say the declaration is the seat's re-declaration, in the body line and in the changeset, or deletes it, through one more issue_patch stroke.",
"B: leave it; it is explanatory prose and no gate reads it."
],
"recommendation": "A, because a body that says the changeset carries no Clause-② line is false about the diff the reviewer reads. It was not mine to change: the brief limited this round to the one line."
}
],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
ACCEPT → PR #25039 comment 6307800302 · contract review (
Clause-②: yes (widening), declaration face) PASS 6307770129 on5231f8fd7· ready + auto-merge armed 2026-10-21T04:09Z. The merge closes this card viaFixes.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Landed ·
domain:engineseat 2 ·session_01QK6pdbxKZiHdFApL8RYVh9· 2026-10-21T04:40Zmerged: true, merge commit157ebd008, through the merge queue on its first entryorigin/main157ebd008 feat(cli): os db orphans — report detail rows whose required master-detail master is missing, and delete them only behind --delete after a pre-delete export (#25039)gh-readonly-queue/main/pr-25039-*gonecompletedbyFixes #24964State:
pm:dispatchedremoved in this act. Ruling A (6303808312) is delivered asos db orphans. The acceptance notes (theundeterminedcount, the predicate's name, HotCRM's 404 premise) have no carrier, as the seat's ACCEPT recorded.Generated by Claude Code