Repository navigation
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
Description
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)
POST /api/v1/data/import/jobs/:jobId/undo,packages/rest/src/data-action-routes.tsabout:647. Its restore and delete writes always carryskipAutomations: trueandskipStateMachine: true, pluspreserveAuditfor a historical job, for any caller the door lets undo the job.- The values are server-chosen, not caller-chosen, so 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's fix (the import doors honour a control opt-out only for the system principal; PR fix(rest)!: the import doors honour a control opt-out only for the system principal #24379) does not cover this door by its letter.
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
skipAutomationsdescribe. - 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
- 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 and PR fix(rest)!: the import doors honour a control opt-out only for the system principal #24379: the import doors refuse a control opt-out to every caller but the system principal.
- runtime: any signed-in member can run a
runAs: 'system'flow throughPOST /automation/:name/trigger— the door checks only anonymity, so an elevated sub-flow is an elevated door for everyone #22310 (closed, p0): "an elevated sub-flow is an elevated door for everyone".
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
objectstack-fleet commentedon Oct 18, 2026
需裁 · p1 ·
security·target:v18·area:access·domain:cli·needs-user-decision· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T18:54ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.⚠️ 本评论不含复现。
为什么要你定,而不是直接派:
origin/main:packages/rest/src/data-action-routes.ts:620附近,恒定带skipStateMachine: true。skipAutomations说明就是这么写的)推荐 B。
执行
pm:queue,交domain:cli,与 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)同族、串行在它之后。skipAutomations说明里写明「撤销是例外」。Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
席位意见(增量)· director seat (summon #43 continuation 5,
session_01QJC7i981TSs9L1CGJK59Ax), read as batch #366 item 2, 2026-10-19T00:08ZOn the
domain:cliseat 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/main44a5011fe623:skipAutomations: trueandskipStateMachine: truefor every caller, pluspreserveAuditfor a historical job (packages/rest/src/data-action-routes.ts:647–:649). Holds; server-chosen, not caller-chosen.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)
preserveAuditfor a historical job is unchanged; (4) the spec'sskipAutomationsdescribe 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;②③④ 是否翻转:否。
objectstack-fleet commentedon Oct 19, 2026
Ruling: batch #366 item 2 · letter B · maintainer 「24380 B」 2026-10-19T00:29Z
Director seat, summon #43 continuation 5,
session_01QJC7i981TSs9L1CGJK59Ax, on thedomain:cliseat 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):
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 keepsskipAutomations/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.preserveAuditfor a historical job is unchanged.skipAutomationsdescribe gains one sentence: the import undo door is not an exception to item 1's rule.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:queuein this act; thedomain:cliseat dispatches it. No reproduction is written on the card.objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 5 ·
domain:cliseat 1 · p1 · security · 2026-10-19T00:40ZSession:
session_01SYEJqDL3NXxrJRBVqaMu9TAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24380-import-undo-controlsWorktree:
objectstack-issue-24380Domain:
domain:cliSeat:
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:preserveAuditfor a historical job is unchanged;skipAutomationsdescribe states the undo door is not an exception;Disclosure: the maintainer's ruling of 2026-08-18 on skills: five principle gaps the #9296 wave exposed in checklist-test / checklist-author / dogfood-verification #9387 holds. No reproduction goes into the PR body, a report or a comment; code positions and the fix's behaviour are published, and tests pin the fixed behaviour.
File surface (read on
origin/mainee882b845; line numbers move; stop on a breach and explain it in the report):packages/rest/src/data-action-routes.ts, thePOST …/import/jobs/:jobId/undoroute (about:604–:680; the write context withskipAutomations: true,skipStateMachine: trueabout:647–:650; the restore and delete writes about:656–:666). The skips are kept only when the caller is the system principal: the same predicate PR fix(rest)!: the import doors honour a control opt-out only for the system principal #24379 (741aa85da) uses at the import doors. A refused write is caught per row and reported in the undo result and in the job's stored history; the job is not marked reverted for a refused row as if it succeeded. The engine-owned stamp write (about:670–:676,isSystem: true, ADR-0103) is unchanged.packages/spec/src/data/hook.zod.ts(about:1006): theskipAutomations.describenames "import undo" as a skip case, which this change makes false for a non-system caller; it gains the ruling's sentence. Any other spec describe or published doc that names the undo as a skip case is corrected the same way. Generated docs are regenerated by the repo's generator, never by hand. This isdomain:spec's file, carried as the ruled execution and declared on [PM seat] domain:spec — 🟢 os-tesla · session_013odhA26dvzy7pQmevdTTvy #6017 in this act.Clause-②: yes (narrowing)), and the dev says so.preserveAudit.@objectstack/rest(minor,fix(rest)!:, a line-initialClause-②: no (narrowing)oryes (narrowing), one ADR-0087 disposition marker, a**BREAKING**sentence that names the cost the ruling names), plus apatchon@objectstack/specfor the describe.packages/core's import runner unless the undo writes go through it (then stop and report); no release act.Container & model:
M,mode:subagent,model: default tierfor the build. Ceiling:CONTRACT_REVIEW_TIERfor one review before the queue (a narrowing, and a published schema describe).Clause-②: no (narrowing)
Basis for the Clause-② line: the undo door stops skipping an app's declared controls for a non-system caller; nothing newly accepted. If a published response field is added, the line becomes
yes (narrowing).Responsibility: the REST undo route passes server-chosen
skipAutomations/skipStateMachinefor every caller (packages/rest, this lane's) | none: PR fix(rest)!: the import doors honour a control opt-out only for the system principal #24379's gate covers only the caller-chosen opt-outs at the import doors | any user who may undo an import job; detail withheld under the disclosure rulingThread-read: 6257104178
Serial constraints cleared:
741aa85da; the ruling names this card serial after it.data-action-routes.ts, the import files orhook.zod.ts.domain:specseat 2, in flight, no PR yet) nameshook.zod.ts, for a different field. Whichever lands second mergesmainand regenerates throughscripts/pm/os-regen-merge.sh.packages/cloud-connection,packages/mcp). Batch 2: this and cloud-connection and mcp copy, the rest of cloud#3002 ruling A: 14 user-visible "this environment" strings need the per-string reading (split from #24368) #24404.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Dev interrupted, re-dispatched ·
domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-19T01:44Z. The session's container restarted, and the dev run under claim6257221825stopped before it reported. Its work is on the remote:claude/issue-24380-import-undo-controlsata23786da7(5 commits). No PR is open yet. The claim stands unchanged. A fresh dev resumes fromclaude/issue-24380-import-undo-controls@a23786da7and never restarts from zero.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
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
objectstack-fleet commentedon Oct 19, 2026
ACCEPT (lane review) → PR #24431 comment
6258459177· head79b4ccf16. ⛔ Not queueable yet: aCONTRACT_REVIEW_TIERreview is running in an isolated reviewer, andneeds:contract-reviewis 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
objectstack-fleet commentedon Oct 19, 2026
Contract review FAIL on ② → patch round 1 ·
domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-19T02:57Z. Record6258647218: the@objectstack/specchangeset must declareyes (narrowing)(a requiredfailureskey narrows the response schema), and the seat's lane ACCEPT missed it. REWORK → PR #24431 comment6258663796. The round carries the changeset fix, plus three lines this PR made stale: theundo_logdescription (declared on #6367), a tripwirewhy, and anexecution-contextTSDoc. A fresh record is owed on the new head. No reproduction is written (#9387).Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
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
objectstack-fleet commentedon Oct 19, 2026
Contract review → PR #24431 comment 6259264431:
CONTRACT_REVIEW_TIER, head83425d5bc, PASS. The spec changeset now declares the narrowing.needs:contract-reviewis 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
objectstack-fleet commentedon Oct 19, 2026
Landed: PR #24431 →
adb7910f8·domain:cliseat 1 ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-19T04:43Zmerged: true, merge commitadb7910f8, merged 2026-10-19T04:42Z through the merge queue (enqueued 2026-10-19T03:57Z)origin/mainadb7910f8is an ancestor oforigin/main, with a single parent (squash). The 17 files and +584 / −48 match what was reviewed at83425d5bcgh-readonly-queue/main/*pr-24431*head remains6257104178→ claim6257221825→ the first dev run lost to a container restart (note on this card) → resumed froma23786da7→ report6258416381;6258459177on the PR, pointer6258475538;CONTRACT_REVIEW_TIERFAIL6258647218(the spec changeset's arm) → REWORK6258663796, pointer6258678601→ report6259079305→ the seat's 11 body edits → amendment6259110796;6259264431, pointer6259308149.6258416381the lane ACCEPT. That id is the dev's report; the ACCEPT is6258459177.6257228801, [PM seat] domain:engine — ⏳ vacant #6367 (theundo_logdescription).failures, kept in the job's undo log for a retry, and never counted as reverted.reverted_atis stamped only on a full revert, andpreserveAuditis unchanged.@objectstack/restand@objectstack/specship asminor, each in the narrowing shape (fix(...)!:, one ADR-0087 marker,**BREAKING**).success: falsewithfailures, after #24380) shows no message, and the job is still listed as undoable objectui#13015,Blocked-by: #24380, which is now closed.completedon the merge.pm:dispatchedis removed in the stroke after this comment.Generated by Claude Code