Skip to content

security: the import undo door writes with skipAutomations and skipStateMachine for any caller who may undo — what an undo owes an app's controls (detail withheld under the disclosure ruling) #24380

Closed

Description

@objectstack-fleet

Ruled: 6257104178 · letter B · 2026-10-19T00:30Z

Security (class a), measured, detail withheld pending maintainer. The maintainer's disclosure ruling (2026-08-18, recorded on #9387) holds: no reproduction is published here. The measured reading stays with the filing seat and the maintainer. Filed by the domain:cli seat 1 (#6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T, from #24348's dev report (6253157395, an open question and a finding). ⛔ Not a claim. Grade and lane are triage's; the door is packages/rest, domain:cli by the lane table.

Where (read on objectstack PR #24379's head 0b764a4ab, the same on main)

The question

An app's declared business controls are its approval flows and other record-change triggers, its metadata-bound hooks (a closed-record freeze, say), its state_machine rule and its read-only protection. What does an undo owe them when the caller is not the system principal? Options as the dev put them:

  • A. Keep as is: reversal skips by design, per the spec's skipAutomations describe.
  • B. Run a non-system caller's undo writes under the object's hooks and state machine, and report a refused restore per row. The cost: undoing a normal import may re-fire record-change automation.
  • C. Refuse to undo, before any write, the rows a control would now refuse.

The dev recommends B as the option consistent with #24348's rule ("only the system principal may take an app's declared controls off a write").

Governing text

Dedupe

The dev's dedupe words: import undo skipAutomations skipStateMachine reversal controls non-system. #24348 is the sibling (the caller-chosen opt-out), and no other open card covers the undo door.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    需裁 · p1 · security · target:v18 · area:access · domain:cli · needs-user-decision · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-18T18:54Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. ⚠️ 本评论不含复现。

    为什么要你定,而不是直接派:

    选项 做法 后果 工作量
    A · 维持 撤销一律跳过触发器、钩子和状态机(spec 的 skipAutomations 说明就是这么写的) 能撤销的人可以借撤销绕过审批和冻结,与 #24348 定下的规则相矛盾 0
    B · 非系统主体的撤销照样走管控(席位推荐) 撤销时按对象的钩子和状态机执行,被拒的行逐行报告 与 #24348 的规则一致。代价是撤销一次普通导入时,可能再次触发记录变更类自动化(比如再发一次通知) 小到中,在 rest 侧
    C · 先判后撤 撤销前先判断哪些行现在会被管控拒绝,这些行整行不撤;其余行仍然跳过管控 不会重复触发自动化。但仍有部分写入不经管控,规则不统一 中

    推荐 B。

    • 长远合理性: 只有一条规则,「只有系统主体能把应用声明的管控从一次写入上拿掉」,所有门统一适用。
    • 业务需求: 撤销本来就是一次真实的数据变更,应用的审批和冻结理应生效。
    • 防 AI 犯错: A 和 C 都会留下一个例外,后来的作者容易误用。
    • 不扩散: B 不新增声明面。

    执行


    Generated by Claude Code

  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    priority:p1High: required for production / M2
    on Oct 18, 2026
  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量)· director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax), read as batch #366 item 2, 2026-10-19T00:08Z

    On the domain:cli seat 1's security card (detail withheld under the disclosure ruling; B recommended by the dev and by triage 6253801522: a non-system caller's undo runs under the object's hooks and state machine, refused rows reported; A: keep the server-chosen skips; C: refuse before any write the rows a control would refuse). The maintainer's words in this session's chat: 「继续决裁」.

    Premises re-run in this act — objectstack origin/main 44a5011fe623:

    1. The undo door's restore and delete writes carry skipAutomations: true and skipStateMachine: true for every caller, plus preserveAudit for a historical job (packages/rest/src/data-action-routes.ts:647–:649). Holds; server-chosen, not caller-chosen.
    2. The sibling rule is in force: PR fix(rest)!: the import doors honour a control opt-out only for the system principal #24379 (security: any user who may import can send runAutomations: false and skip every trigger and metadata-bound hook, so a sales rep bypasses an approval gate and a closed-record freeze through POST /data/:object/import #24348, 「the import doors honour a control opt-out only for the system principal」) is merged. Holds — so the undo door is now the one import door where a non-system caller still gets the skips, by the server's hand.
    3. No reproduction is on this card; the measured reading stays with the filing seat and the maintainer (the 2026-08-18 disclosure ruling on skills: five principle gaps the #9296 wave exposed in checklist-test / checklist-author / dogfood-verification #9387). Holds.

    Facets: ① agree with B — one rule for every door (「only the system principal may take an app's declared controls off a write」, #24348), no undo exception to remember; A keeps an elevated door for everyone who may undo (#22310's shape), C leaves part of an undo outside the controls · ② an undo is a real data change on a live app; its approval flows, freezes and state machine are the app's own rules, and they should hold for the person who clicks undo as for anyone else; the cost B names — a record-change automation may fire again on an undone import — is the app's declared behaviour, not a defect · ③ A and C each leave an exception a later author misreads; B's refused rows must be reported loudly per row, never swallowed into a 「success」 · ④ B adds no declared surface; the change stays in packages/rest.

    Recommend B. 口径, binding with the letter: (1) a non-system caller's undo writes run under the object's hooks, triggers and state machine; the system principal keeps the skips; (2) a row a control refuses is reported per row in the undo result and the job's history, and the undo does not report success for it; (3) preserveAudit for a historical job is unchanged; (4) the spec's skipAutomations describe gains the sentence that the undo door is not an exception. Not taken: A (an elevated door for everyone who may undo), C (a partial control). Workload: rest S–M, domain:cli, serial after PR #24379 (merged), with a pin per the rule and one per refused-row report.

    os-decision-facets

    • ① 项目长远合理性:同意 B —— 所有门一条规则,只有系统主体能把应用声明的管控从写入上拿掉;A 给能撤销的人留一扇提权门,C 让部分撤销绕过管控。
    • ② 实际业务拉动:撤销是活应用上的真实数据变更,审批、冻结、状态机理应对点撤销的人生效;B 的代价(自动化可能再触发)是应用声明的行为。
    • ③ 防 AI 犯错:A、C 各留一个例外;B 被拒的行必须逐行响亮报告,不能吞进「成功」。
    • ④ 创业阶段不扩散:B 不新增声明面,只动 rest。

    自检:只看①选 B;②③④ 是否翻转:否。

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #366 item 2 · letter B · maintainer 「24380 B」 2026-10-19T00:29Z

    Director seat, summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax, on the domain:cli seat 1's security card (B recommended by the dev, triage 6253801522 and the director delta 6256872219; detail withheld under the 2026-08-18 disclosure ruling on #9387) (opening marker #12708, comment 6247058702). The maintainer's words in this session's chat, verbatim: 「24377 需要的话,Q1 你现在就可以派测试 ,cloud 相关任务加到cloud车道。 其他Q问题也帮我深度调研和分析确认。 24380 B; 24378 结合主流相关平台,帮我深度分析;3036 A;2167 A」.

    What the ruling binds (letter B — a non-system caller's undo runs under the app's declared controls):

    1. POST /api/v1/data/import/jobs/:jobId/undo (packages/rest/src/data-action-routes.ts, the restore and delete writes about :647–:649): for any caller but the system principal, the writes run under the object's hooks, record-change triggers and state machine; the system principal keeps skipAutomations / skipStateMachine. The same rule as security: any user who may import can send runAutomations: false and skip every trigger and metadata-bound hook, so a sales rep bypasses an approval gate and a closed-record freeze through POST /data/:object/import #24348 / PR fix(rest)!: the import doors honour a control opt-out only for the system principal #24379: only the system principal may take an app's declared controls off a write.
    2. A row a control refuses is reported per row in the undo result and in the job's history; the undo does not report success for that row; nothing is swallowed.
    3. preserveAudit for a historical job is unchanged.
    4. The spec's skipAutomations describe gains one sentence: the import undo door is not an exception to item 1's rule.
    5. The cost is named, not hidden: undoing an ordinary import may fire record-change automation again; that is the app's declared behaviour.

    Not taken: A (an elevated door for everyone who may undo, #22310's shape), C (a partial control with a second rule to remember).

    Workload of the letter: rest S–M, domain:cli, serial after PR #24379 (merged); a pin per the rule and one per the refused-row report.

    Execution: this card is the execution card — needs-user-decision → pm:queue in this act; the domain:cli seat dispatches it. No reproduction is written on the card.

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5 · domain:cli seat 1 · p1 · security · 2026-10-19T00:40Z
    Session: session_01SYEJqDL3NXxrJRBVqaMu9T
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24380-import-undo-controls
    Worktree: objectstack-issue-24380
    Domain: domain:cli
    Seat: domain:cli#1 (seat post #6024)
    Ruling-ref: 6257104178 (batch #366 item 2, letter B, maintainer 「24380 B」 2026-10-19T00:29Z), re-read at its source in this act. Items 1–5 bind:


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Dev interrupted, re-dispatched · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-19T01:44Z. The session's container restarted, and the dev run under claim 6257221825 stopped before it reported. Its work is on the remote: claude/issue-24380-import-undo-controls at a23786da7 (5 commits). No PR is open yet. The claim stands unchanged. A fresh dev resumes from claude/issue-24380-import-undo-controls@a23786da7 and never restarts from zero.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 24380,
     "status": "done",
     "branch": "claude/issue-24380-import-undo-controls",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/24431",
     "session": "session_01SYEJqDL3NXxrJRBVqaMu9T",
     "premise_still_valid": true,
     "summary": "Resumed from claude/issue-24380-import-undo-controls@a23786da7 and finished the ruled letter B (ruling 6257104178): POST /api/v1/data/import/jobs/:jobId/undo (packages/rest/src/data-action-routes.ts, importUndoWriteContext) keeps skipAutomations/skipStateMachine for the system principal only (ExecutionContext.isSystem === true, the predicate of 741aa85da's importControlOptOutRefusal); every other caller undoes under the object's hooks, record-change triggers and state machine; a historical job keeps preserveAudit for every caller; the engine-owned job-row write keeps isSystem. A reversal that throws is reported per row in a new failures array ({ id, action, code, error }, classified as PATCH/DELETE /data/:object/:id classify the same error), counted in failed, makes success false, and is kept with its row in the job's stored undo log; reverted_at is stamped only when every row was reverted, so the job stays undoable and a second undo retries only the remaining rows. All five resumed commits were kept as-is after reading each against the ruling and the claim (fec4827ec route, 72382a087 spec describes plus the failures key plus docs rows, e2d71b64d generator output, 629f02582 the two changesets, a23786da7 test typing); this run added only 79b4ccf16, a clean merge of origin/main (8baeda520, nothing generated touched), then opened draft PR objectstack-ai/objectstack#24431 assigned to zhuangjianguo with zero labels (none named by the dispatch; skip-changeset does not apply). Clause-2 is consistent: PR body and the @objectstack/rest changeset carry `Clause-②: yes (narrowing)` with the one ADR-0087 marker; the @objectstack/spec changeset is minor with `Clause-②: yes (widening)`, since spec only gains the failures key and narrows nothing. origin/main has since gained e22defb13 (plugin-auth only, disjoint); not merged, per os-dev.md's after-PR merge rule. Hold-offs held: the import doors and their gate untouched, no new permission/capability/config key, the undo writes go through the protocol's deleteData/updateData and not @objectstack/core's import runner, no release act.",
     "tests": "All at head 79b4ccf16 under scripts/pm/os-verify-lock.sh. (1) pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 --reporter=verbose src/import-undo-controls.test.ts: 6 passed (real routes, real ObjectQL, real protocol, better-sqlite3 :memory:; fixture approval-gate hook, closed-record freeze hook on update and delete, state_machine with a terminal state): signed-in undo runs the hooks and reverts every row (default path, failures []); system principal keeps the skips; signed-in undo walks the state machine, the refused restore is reported per row in the answer and the stored undo log, not counted as reverted, job stays undoable, the system principal's retry reverts only that row and stamps the job; freeze refuses a delete and a restore, both reported and kept; historical job keeps preserveAudit with no skip for a signed-in caller and beside the skips for the system principal. (2) rest full: vitest run --project local --maxWorkers=2: Test Files 299 passed (299), Tests 6085 passed, 326 skipped. (3) pnpm --filter @objectstack/rest run typecheck: TYPECHECK_EXIT=0, check:test-typecheck OK; tsc -p tsconfig.test.json --listFilesOnly includes import-undo-controls.test.ts (count 1). (4) pnpm --filter @objectstack/spec run typecheck: exit 0; spec local suites src/api/export.test.ts, src/data/hook.test.ts, src/kernel/execution-context.test.ts, src/type-alias-convention.pin.test.ts: 4 files, 169 passed; --project repo src/api/export-job-family-retirement.test.ts: 7 passed. (5) eslint --no-inline-config over the 6 changed .ts files: exit 0, no output. ABLATION (premise measurement, one-off, not kept, implementation committed first): node scripts/ablation-replace.mjs --file packages/rest/src/data-action-routes.ts replaced the system-principal conditional in importUndoWriteContext with unconditional skips, anchor x1 -> x0, replacement x0 -> x1, blob bf6a57a75bbf -> 01b7721efd21 ('ok mutation landed'), wrapped run of the suite: Tests 4 failed | 2 passed (6), exactly the four signed-in-caller pins red on their own assertions (approval_status stayed approved; failed 0 where 1; deleted 1 where the freeze should refuse; skipAutomations present on the historical undo), the two system-principal pins green; restore: blob after restore bf6a57a75bbf == blob at HEAD, git diff HEAD empty ('ok restored'). The rest suite resolves data-action-routes.ts from its own src via a relative import, so no build sat between mutation and run (no dist leg).",
     "gates": "Local subset at 79b4ccf16: node scripts/pm/dispatch-gates.mjs --commands -> 111 commands (14 paths vs merge base 8baeda520, +571/-44, under the 3000-line threshold); every one run with its exit code captured before any pipe: 111 x exit 0. --ran reconciliation: 'Run reconciliation — 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. Verdict lines: check-adr-0087-registration '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' (24380-import-undo-controls.md, BREAKING+bang+clause-②-narrowing, not-required (no-migration-prescription)); check-changeset-no-major 'This diff introduces no major bump', level axis NOT APPLICABLE locally, so also driven offline with the PR body via --event: 'this PR declares clause-② yes (narrowing), and no package whose published source ... it moves is graded patch', exit 0; check-empty-changeset 2 declaring changesets added; check-system-context-census OK (144 read sites, 138 symbols cited against 138 required); spec check:generated 'All 14 generated artifacts are up to date', check:docs '227 generated files in sync', check:api-surface unchanged, check:authorable-surface and check:strictness-ledger green; check:nul-bytes OK. Local-only check:dev-prereqs (spec build touched): exit 0. CI (PR objectstack-ai/objectstack#24431 check-runs on 79b4ccf16 at report time): completed success — Check Documentation Links, 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, filter; skipped by their if: (main battery) — Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Temporal Conformance, Packed-tarball smoke; in_progress — Build Core, Build Docs, Check Changeset, Governed Surface Queue Guard, Test Core 1-6/6, Type Check (consumer gates, debt ledger, source gates, workspace), Lint (CI release and engine gates, ESLint, PM dispatch-gates, docs and code guards, repo and tooling gates, three scoped lanes), Spec property liveness, Flag docs affected by code changes, Dogfood Regression Gate, Auto Label. Not waited on, per os-dev.md.",
     "mcp_calls": "0 — no MCP GitHub tool was called",
     "api_writes": "4 REST requests in 2 fleet-write relay strokes (repository_dispatch to objectstack-ai/objectstack, which the relay counts as the transport, not a write): pr_create (run 39190323615) = POST /repos/objectstack-ai/objectstack/pulls (draft, read back 11799 of 11799 bytes identical) + POST /repos/objectstack-ai/objectstack/issues/24431/assignees (zhuangjianguo listed); this os-dev-report = POST /repos/objectstack-ai/objectstack/issues/24380/comments. No label write. git push x1 (79b4ccf16) is not REST.",
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: 承接者:无 · noted in PR objectstack-ai/objectstack#24431 Acceptance notes, not filed (a code reading, not measured) · objectui packages/plugin-grid/src/ImportWizard.tsx useImportJobUndo (objectui 79cb8c0) awaits ds.undoImportJob and never reads its answer, so a partial undo now shows no message for the rows left unreverted while the job stays listed undoable; if a seat measures it, it is an objectui card with Blocked-by on PR objectstack-ai/objectstack#24431 · dedupe words: ImportWizard undo failures partial unreverted message",
      "carrier: 承接者:无 · noted in Acceptance notes, not filed (polish) · packages/platform-objects/src/audit/sys-import-job.object.ts undo_log description names created/updated only; a partial undo's log also carries failures · dedupe words: sys_import_job undo_log description failures",
      "carrier: 承接者:无 · noted in Acceptance notes, not filed · packages/objectql/src/engine.ts comment above the skipAutomations read lists 'import undo' among setters, still true of the system principal's undo; internal comment, not a published describe · dedupe words: skipAutomations comment import undo engine",
      "carrier: 承接者:无 · noted in Acceptance notes, not filed · docs/qa/platform-checklist/areas/records-forms.json records-forms.import-job-undo-cancel grades an undo that leaves rows behind a FAIL; still true on its fixture with no refusing control, while a refused row is now left and reported by design · dedupe words: import-job-undo-cancel checklist leaves rows behind"
     ]
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT (lane review) → PR #24431 comment 6258459177 · head 79b4ccf16. ⛔ Not queueable yet: a CONTRACT_REVIEW_TIER review is running in an isolated reviewer, and needs:contract-review is on the PR. Filed from the review: objectstack-ai/objectui#13015, the objectui Import Wizard dropping a partial undo's answer (Blocked-by: objectstack-ai/objectstack#24380). No reproduction is written (#9387).


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Contract review FAIL on ② → patch round 1 · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-19T02:57Z. Record 6258647218: the @objectstack/spec changeset must declare yes (narrowing) (a required failures key narrows the response schema), and the seat's lane ACCEPT missed it. REWORK → PR #24431 comment 6258663796. The round carries the changeset fix, plus three lines this PR made stale: the undo_log description (declared on #6367), a tripwire why, and an execution-context TSDoc. A fresh record is owed on the new head. No reproduction is written (#9387).


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 24380,
     "patch_round": 1,
     "status": "done",
     "branch": "claude/issue-24380-import-undo-controls",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/24431",
     "session": "session_01SYEJqDL3NXxrJRBVqaMu9T",
     "premise_still_valid": true,
     "summary": "Patch round 1 on head 79b4ccf16 -> 83425d5bc, answering record 6258647218 (② FAIL on the spec changeset's arm) and the seat's REWORK 6258663796; ① and the rest changeset untouched. (1) .changeset/24380-undo-response-failures.md rewritten: '@objectstack/spec': minor, summary fix(spec)!:, line-initial `Clause-②: yes (narrowing)`, exactly one ADR-0087 HTML-comment marker not-required (no-migration-prescription) with the reason that no authored key or stored shape moves, and a **BREAKING** (...) sentence: a response parsed with UndoImportJobResponseSchema is refused without failures; code constructing an UndoImportJobResponse must set it, [] on a full revert; readers of client.data.undoImportJob are unchanged; ending 'shipped as `minor` under the launch-window convention for breaking changes'. failures stays required (no .optional()). (2) packages/platform-objects/src/audit/sys-import-job.object.ts: only the undo_log description changed, adding that a partial undo keeps only the unreverted rows there with a failures list (id, action, code, refusal); en.objects.generated.ts regenerated with node scripts/check-i18n-bundles.mjs --filter=platform-objects --write (the translated locales keep their values in merge mode; check:i18n and check:i18n-stale-fill green). (3) rest-write-response-internal-fields.tripwire.test.ts: the undo row keeps no-record-echo, why now 'Undo summary: counts + per-row `failures` (record id, action, code, refusal sentence), never a record body.' (4) execution-context.zod.ts: the skipAutomations TSDoc now says runImport sets it from its caller's runAutomations: false, the import doors admit that opt-out over HTTP from the system principal alone, an in-process caller of the runner passes its own setting, and the import undo sets it only for the system principal; the skipStateMachine sentence this PR added was only re-wrapped. Four commits: aabf9edb5, 7c779806f, 483069d1f, 83425d5bc. The PR body was not patched; body_edits below carries exact old/new pairs against the live body (11 pairs, each old string occurring exactly once; the patched body passes the level axis offline). Noted, not touched: the live PR body's first line now reads 'Fixes objectstack-ai/objectstack#24380', not as created; another actor's edit, left as is. The previous worktree had been removed after round 0, so this round re-created /home/user/objectstack-issue-24380 on the same branch at 79b4ccf16 and ran pnpm install --frozen-lockfile.",
     "tests": "At head 83425d5bc under scripts/pm/os-verify-lock.sh: pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/rest-write-response-internal-fields.tripwire.test.ts src/import-undo-controls.test.ts: Test Files 2 passed, Tests 21 passed (REST_TEST_EXIT=0); pnpm --filter @objectstack/rest run typecheck: REST_TC_EXIT=0; pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2: Test Files 77 passed, Tests 1184 passed (PO_TEST_EXIT=0); pnpm --filter @objectstack/platform-objects run typecheck: PO_TC_EXIT=0; pnpm --filter @objectstack/spec run typecheck: SPEC_TC_EXIT=0; pnpm --filter @objectstack/spec exec vitest run --project local src/kernel/execution-context.test.ts src/api/export.test.ts: 2 files, 70 passed (SPEC_TEST_EXIT=0). eslint --no-inline-config over the four changed files (tripwire test, sys-import-job.object.ts, en.objects.generated.ts, execution-context.zod.ts): exit 0, no output. No ablation this round: no tested behaviour changed (a changeset, a describe, a TSDoc comment and a ledger 'why' string).",
     "gates": "Changeset gates at 83425d5bc: check-changeset-no-major.mjs --base origin/main exit 0 ('This diff introduces no major bump'); offline with the live PR body via --event: exit 0, '✓ LEVEL AXIS: this PR declares clause-② yes (narrowing), and no package whose published source ... it moves is graded patch'; the same with the body_edits applied: exit 0, same line. check-adr-0087-registration.mjs --base origin/main exit 0: '2 declared-breaking changeset(s), each carrying an ADR-0087 disposition' — 24380-import-undo-controls.md and 24380-undo-response-failures.md, each [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription). Derived: node scripts/pm/dispatch-gates.mjs --commands -> 113 commands (17 paths vs merge base 8baeda520, +584/-48; new vs round 0: pnpm check:i18n, pnpm check:i18n-stale-fill); all 113 run with exit codes captured before any pipe. Two first exited 3 (PREREQUISITE NOT MET in the fresh worktree: spec check:skill-examples needed client-react dist, check:dual-build-cjs-loads needed 11 package dists); after one turbo build of those 11 packages under the lock (exit 0) both re-ran to exit 0, and the record carries those codes. --ran: 'Run reconciliation — 113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. Verdicts: check:i18n 'OK (10 package(s) — all bundles in sync, no undeclared authoring keys)'; check:i18n-stale-fill OK; check:keyed-text-bounds green; check-system-context-census OK (138 of 138); spec check:generated 'All 14 generated artifacts are up to date', check:docs '227 generated files in sync'; check:nul-bytes OK. Local-only check:dev-prereqs exit 0. CI on 79b4ccf16 read before pushing: nothing red; Test Core (2/6) was later cancelled by this round's push ('Canceling since a higher priority waiting request ... exists', its test step already success; aggregate Test Core success). CI on 83425d5bc at report time: 32 success, 5 skipped by their if: (main battery), 3 in_progress (Test Core 3/6, 4/6, 6/6), none failed. Not waited on.",
     "mcp_calls": "0 — no MCP GitHub tool was called",
     "api_writes": "1 REST request in 1 fleet-write relay stroke: this os-dev-report = POST /repos/objectstack-ai/objectstack/issues/24380/comments. No PR body, label or assignee write. git push x2 (483069d1f, 83425d5bc) is not REST.",
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: 承接者:无 · noted, not filed · the translated (zh-CN, ja-JP, es-ES) help text for sys_import_job.undo_log keeps its earlier sentence after the en regeneration (merge mode); check:i18n-stale-fill green · dedupe words: undo_log help translation partial undo failures",
      "carrier: 承接者:无 · noted by the record, not filed · GET /import/jobs/:jobId and /results do not project the stored failures; the undo answer and the job row carry them · dedupe words: import job progress failures projection undo"
     ],
     "body_edits": [
      {
       "section": "Clause-② basis",
       "old": "Basis for the Clause-② line (from the claim, `6257221825`, which names this case): the undo door stops taking an app's declared controls off its writes for every caller but the system principal, which is a narrowing; and the undo's published answer, `UndoImportJobResponseSchema`, gains one key, `failures`, which is a widening. `yes (narrowing)` declares both. The `@objectstack/rest` changeset carries the same line and the PR's one ADR-0087 marker; the `@objectstack/spec` changeset declares `yes (widening)` because the only thing that package gains is the new key, and it narrows nothing.",
       "new": "Basis for the Clause-② line (from the claim, `6257221825`, which names this case): the undo door stops taking an app's declared controls off its writes for every caller but the system principal, which is a narrowing; and the undo's published answer, `UndoImportJobResponseSchema`, gains one key, `failures`, which widens the answer and, because the key is required, also narrows the schema's accept set: a response without it is refused, and code that constructs an `UndoImportJobResponse` must set it (the shape `ListAiConversationsResponseSchema.hasMore` set). `yes (narrowing)` declares both. Both changesets carry the same line, each with its one ADR-0087 marker."
      },
      {
       "section": "Spec and docs (execution-context)",
       "old": "- `packages/spec/src/kernel/execution-context.zod.ts`: the `skipAutomations` and `skipStateMachine` TSDoc say the same.",
       "new": "- `packages/spec/src/kernel/execution-context.zod.ts`: the `skipAutomations` TSDoc says the data-import runner (`runImport`) sets it from its caller's `runAutomations: false`, which the import doors admit over HTTP from the system principal alone while an in-process caller of the runner passes its own setting, and that the import undo sets it only when the system principal undoes a job; the `skipStateMachine` TSDoc says the same of the undo."
      },
      {
       "section": "Spec and docs (files changed: platform-objects)",
       "old": "- Generated artifacts come from the generators only: `content/docs/references/api/export.mdx`, `content/docs/references/data/hook.mdx`, `packages/spec/authorable-surface/api.json` and the strictness-ledger counts.",
       "new": "- Generated artifacts come from the generators only: `content/docs/references/api/export.mdx`, `content/docs/references/data/hook.mdx`, `packages/spec/authorable-surface/api.json` and the strictness-ledger counts.\n- `packages/platform-objects/src/audit/sys-import-job.object.ts`: the `undo_log` field's description adds that a partial undo keeps only the unreverted rows there, with a `failures` list giving each one's id, action, code and refusal. Its `en` i18n bundle (`packages/platform-objects/src/apps/translations/en.objects.generated.ts`) is regenerated with `node scripts/check-i18n-bundles.mjs --filter=platform-objects --write`; the translated locales keep their values (merge mode)."
      },
      {
       "section": "Changesets (spec)",
       "old": "- `.changeset/24380-undo-response-failures.md`: `'@objectstack/spec': minor`, `Clause-②: yes (widening)`, for the new response key and the describes.",
       "new": "- `.changeset/24380-undo-response-failures.md`: `'@objectstack/spec': minor`, summary `fix(spec)!:`, `Clause-②: yes (narrowing)`, one ADR-0087 marker (`not-required (no-migration-prescription)`: no authored key or stored shape moves), and a `**BREAKING**` sentence saying who notices the required `failures`: a response parsed with `UndoImportJobResponseSchema` is refused without it; code constructing an `UndoImportJobResponse` must set it, `[]` on a full revert; readers of `client.data.undoImportJob` are unchanged. `failures` stays required."
      },
      {
       "section": "Tests (readings)",
       "old": "Readings at head `79b4ccf16`:\n\n- `pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/import-undo-controls.test.ts`: 6 passed.\n- `pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2`: 299 files passed, 6085 tests passed, 326 skipped.\n- `pnpm --filter @objectstack/rest run typecheck`: exit 0; `check:test-typecheck` OK, and `--listFiles` on `tsconfig.test.json` includes the new suite.\n- `pnpm --filter @objectstack/spec run typecheck`: exit 0. Spec suites over the edited schemas (`src/api/export.test.ts`, `src/data/hook.test.ts`, `src/kernel/execution-context.test.ts`, `src/type-alias-convention.pin.test.ts`): 169 passed; `--project repo src/api/export-job-family-retirement.test.ts`: 7 passed.\n- `eslint --no-inline-config` over the six changed TypeScript files: exit 0, no output.",
       "new": "Readings at head `79b4ccf16`:\n\n- `pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/import-undo-controls.test.ts`: 6 passed.\n- `pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2`: 299 files passed, 6085 tests passed, 326 skipped.\n- `pnpm --filter @objectstack/rest run typecheck`: exit 0; `check:test-typecheck` OK, and `--listFiles` on `tsconfig.test.json` includes the new suite.\n- `pnpm --filter @objectstack/spec run typecheck`: exit 0. Spec suites over the edited schemas (`src/api/export.test.ts`, `src/data/hook.test.ts`, `src/kernel/execution-context.test.ts`, `src/type-alias-convention.pin.test.ts`): 169 passed; `--project repo src/api/export-job-family-retirement.test.ts`: 7 passed.\n- `eslint --no-inline-config` over the six changed TypeScript files: exit 0, no output.\n\nPatch round 1, readings at head `83425d5bc`:\n\n- `pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/rest-write-response-internal-fields.tripwire.test.ts src/import-undo-controls.test.ts`: 2 files, 21 passed. The tripwire's undo row keeps `no-record-echo`; its `why` now says the answer carries counts and per-row `failures` (record id, action, code, refusal sentence), never a record body.\n- `pnpm --filter @objectstack/rest run typecheck`: exit 0.\n- `pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2`: 77 files, 1184 passed; `pnpm --filter @objectstack/platform-objects run typecheck`: exit 0.\n- `pnpm --filter @objectstack/spec run typecheck`: exit 0; `src/kernel/execution-context.test.ts` and `src/api/export.test.ts`: 70 passed.\n- `eslint --no-inline-config` over the four files this round changed: exit 0, no output."
      },
      {
       "section": "Gates (summary)",
       "old": "Local subset at head `79b4ccf16`, the derivation's own list: `node scripts/pm/dispatch-gates.mjs --commands` printed 111 commands for this change set (14 paths vs merge base `8baeda520`, +571 / -44 lines, under the 3000-line threshold). All 111 were run one by one with each exit code captured before any pipe, and all 111 exited 0. `node scripts/pm/dispatch-gates.mjs --ran` over that record: \"111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN\", exit 0. Verdict lines from the families this diff is about:",
       "new": "Local subset at head `79b4ccf16`, the derivation's own list: `node scripts/pm/dispatch-gates.mjs --commands` printed 111 commands for this change set (14 paths vs merge base `8baeda520`, +571 / -44 lines, under the 3000-line threshold). All 111 were run one by one with each exit code captured before any pipe, and all 111 exited 0. `node scripts/pm/dispatch-gates.mjs --ran` over that record: \"111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN\", exit 0. Patch round 1 at head `83425d5bc`: the derivation printed 113 commands (17 paths, +584 / -48; the two new ones are `pnpm check:i18n` and `pnpm check:i18n-stale-fill`); all 113 were run with each exit code captured before any pipe and all 113 exited 0 (two first answered exit 3, prerequisite not met in a fresh worktree, and exited 0 once the named packages were built); `--ran`: \"113 derived, 113 run, 0 NOT-MEASURED, 0 UNRUN\", exit 0. Verdict lines from the families this diff is about, at the round's head:"
      },
      {
       "section": "Gates (ADR-0087)",
       "old": "- `check-adr-0087-registration.mjs --base origin/main`: \"1 declared-breaking changeset(s), each carrying an ADR-0087 disposition\" (`24380-import-undo-controls.md`, signals `BREAKING+bang+clause-②-narrowing`, `not-required (no-migration-prescription)`).",
       "new": "- `check-adr-0087-registration.mjs --base origin/main`: \"2 declared-breaking changeset(s), each carrying an ADR-0087 disposition\" (`24380-import-undo-controls.md` and `24380-undo-response-failures.md`, each with signals `BREAKING+bang+clause-②-narrowing` and `not-required (no-migration-prescription)`)."
      },
      {
       "section": "Gates (i18n, new families)",
       "old": "- `check:nul-bytes` OK; `check:route-envelope` green; `check:cross-package-test-inputs` OK.",
       "new": "- `check:nul-bytes` OK; `check:route-envelope` green; `check:cross-package-test-inputs` OK.\n- `check:i18n` \"OK (10 package(s) — all bundles in sync, no undeclared authoring keys)\"; `check:i18n-stale-fill` OK; `check:keyed-text-bounds` green."
      },
      {
       "section": "Resumed work",
       "old": "This branch resumes a dev run stopped by a container restart before it reported (`a23786da7`, five commits). All five were kept as they were, after reading each against the ruling and the claim; this run merged `origin/main` (no conflict, nothing generated touched) and added no other commit.",
       "new": "This branch resumes a dev run stopped by a container restart before it reported (`a23786da7`, five commits). All five were kept as they were, after reading each against the ruling and the claim; this run merged `origin/main` (no conflict, nothing generated touched) and added no other commit.\n\nPatch round 1 (record `6258647218`, ② FAIL on the spec changeset's arm, and the seat's REWORK) added four commits: `aabf9edb5` (the spec changeset as a narrowing; the `ExecutionContext` TSDoc names `runImport`'s in-process caller), `7c779806f` (the tripwire's undo row), `483069d1f` (the `undo_log` description) and `83425d5bc` (its regenerated `en` bundle). `failures` stays required."
      },
      {
       "section": "Acceptance notes (objectui)",
       "old": "- objectui's Import Wizard (`packages/plugin-grid/src/ImportWizard.tsx`, `useImportJobUndo`, read at objectui `79cb8c0`) awaits the undo and does not read its answer, so after a partial undo it shows no message for the rows left unreverted, and the job stays listed as undoable. A code reading, not measured; no card filed. Carrier: none.",
       "new": "- objectui's Import Wizard (`packages/plugin-grid/src/ImportWizard.tsx`, `useImportJobUndo`, read at objectui `79cb8c0`) awaits the undo and does not read its answer, so after a partial undo it shows no message for the rows left unreverted, and the job stays listed as undoable. A code reading, not measured; filed as #24433."
      },
      {
       "section": "Acceptance notes (undo_log description, now done)",
       "old": "- `packages/platform-objects/src/audit/sys-import-job.object.ts`: the `undo_log` field's description spells the log as created ids and updated snapshots; after a partial undo the log also carries `failures`. Not wrong about the reversal instructions, but it does not name the new key. Left as is; carrier: none.",
       "new": "- `GET /import/jobs/:jobId` and `/results` do not project the stored `failures`; the undo answer and the job row carry them, which meets the ruling's \"job's history\". The three translated locales of the `undo_log` help text keep their earlier sentence (merge mode); `check:i18n-stale-fill` is green."
      }
     ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Contract review → PR #24431 comment 6259264431: CONTRACT_REVIEW_TIER, head 83425d5bc, PASS. The spec changeset now declares the narrowing. needs:contract-review is removed and the pre-checks read clean, so the PR is ready with auto-merge on and lands through the queue. No reproduction is written (#9387).


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #24431 → adb7910f8 · domain:cli seat 1 · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-19T04:43Z

    Reading Result
    PR #24431 object merged: true, merge commit adb7910f8, merged 2026-10-19T04:42Z through the merge queue (enqueued 2026-10-19T03:57Z)
    origin/main adb7910f8 is an ancestor of origin/main, with a single parent (squash). The 17 files and +584 / −48 match what was reviewed at 83425d5bc
    queue branch no gh-readonly-queue/main/*pr-24431* head remains

    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

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p1High: required for production / M2securitytarget:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions