Skip to content

fix(runtime): a declarative update action refuses a caller with no edit grant 403 before the record lookup, as the data door does - #30878

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-y8qx4d
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-y8qx4d

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

domain:server#1 · os-elon-musk · session_016WV4czxVZYV4CcMiCbofw3

Fixes #30848

Clause-②: no
Metadata-protocol: none

Claim: comment 6679693194 (branch claude/project-thread-y8qx4d).

维护者速读

  • 改了什么:声明式更新动作(operation: 'update',REST /actions 与 MCP run_action)在按"读不到这条记录"回 404 之前,先问调用者对该对象有没有编辑权;没有就回 403 PERMISSION_DENIED,与数据接口(PATCH /data、MCP update_record)对同一次写入的回答一致。
  • 为什么改:只读无编辑权的用户改一条看不到的记录(或不存在的 id),动作门答 404、数据门答 403,同一次写入两个答案;文档写明"拒绝先于记录查找"、声明式更新"与用户编辑完全一致"。场景卡 Scenario loader refuses an operation its persona cannot reach (row scope, hidden field or object); fix the three cards it catches #30802 因此无法写出唯一的预期拒绝。
  • 风险与回滚:只在记录加载被拒时多问一次安全服务的 explain(update),正常路径不变;有编辑权的人仍对"看不到的行"和"不存在的 id"回同一个 404,不形成存在性探测。单 PR 可整体回滚。
  • 先看:packages/runtime/src/action-execution.ts(refuseUngrantedDeclarativeUpdate 与 executeDeclarativeUpdateAction 中它的调用点)、packages/runtime/src/declarative-update-refusal-order.pin.test.ts。
  • 两分钟验证:cd packages/runtime && pnpm exec vitest run --project local src/declarative-update-refusal-order.pin.test.ts,预期 20/20 通过。
  • 需要你做的:无,席位落地。

What changes

  • New refuseUngrantedDeclarativeUpdate in @objectstack/runtime (action-execution.ts). executeDeclarativeUpdateAction, the executor both action doors call (REST /actions and MCP run_action), now calls it before it consumes the subject-load verdict (refuseDeniedSubjectLoad), and only when that load was denied.
  • The ask is the security service's explain({ object, operation: 'update' }), the contract's "would the middleware allow this operation?" answer, computed by the enforcement walk. The REST import-template gate already asks create the same way. No row is named in the ask, so the answer cannot depend on whether the id names a row. Nothing is re-derived from permission sets, and nothing changes in plugin-security.
  • A denial, or an explain that throws, refuses with the data door's own refusal: 403 PERMISSION_DENIED with the middleware's user-facing sentence (permission_denied via renderOperationMessage, the caller's locale, the deployment's i18n override). With no security service, or one without explain, nothing is refused here, as at the data door.
  • A delivered row is unchanged: the write that follows carries the caller's identity and the data plane answers its own 403. A caller who may update the object keeps the not-found answer, one answer for a hidden row and a missing id.
  • Docblocks on executeDeclarativeUpdateAction updated to name the new order.

The line the card asked for

On main a2f523aa, invokeBusinessAction loads the subject at packages/runtime/src/action-execution.ts:4406 (const subject = await loadActionSubjectRecord(…)), and the declarative branch refuses on that load's verdict at :4169 (refuseDeniedSubjectLoad(objectName, recordId, subject) in executeDeclarativeUpdateAction) before the write, the one place the object-level edit permission was asked, is attempted. The REST /actions door follows the same sequence (domains/actions.ts, its own load, then the same executor).

Other subject-loading paths (checked, not changed)

Why

content/docs/protocol/kernel/http-protocol.mdx, "Refusals come before the record lookup": a caller who may not update the object gets the same 403 whether or not the record id names a record. content/docs/protocol/objectui/actions.mdx: a declarative update fires with the caller's permissions "exactly as for a user edit". Both sentences were false for the action door. The finding came from #30802 / PR #30846 (travel_expense op 4), which needs one expected refusal for this operation.

Tests

packages/runtime/src/declarative-update-refusal-order.pin.test.ts boots one kernel (the plugin set unresolved-object-name.actions-door.pin.test.ts uses: ObjectQL, sqlite-wasm, Hono, the app, platform objects, auth, security, sharing, REST, dispatcher). Object pin_step is private OWD with a declarative decide action. Two invited members: a reader (default set: read + create) and an editor (an assigned set: read + create + edit). Each member owns one row.

  • Matrix: {reader, editor} × {own row, the other member's row, a missing id} × four doors: PATCH /data, the MCP bridge's update (what update_record calls), POST /actions/pin_step/decide/:id, and the MCP bridge's runAction (what run_action calls). The MCP leg is driven through the real /mcp route with a stand-in mcp service that calls the two bridge members and reports the thrown status and code. The tools' own envelope rendering is @objectstack/mcp's and is not under test here. Expected: the reader gets 403 PERMISSION_DENIED on all three rows at every door; the editor gets 200 on its own row and 404 RECORD_NOT_FOUND on the other two at every door. A refused write leaves the row untouched.
  • Existence-oracle equality: within each door and for each caller, the hidden row and the missing id get the same answer, body included, once the id is written out (8 cases). For the reader, the visible row's refusal at each door is that same answer too, sentence included (4 cases). So the action door's 403 carries the data plane's sentence.
  • Preconditions: each member reads its own row and gets 404 on the other's; the administrator reads both.
Baseline on main's action door (the re-measure)

The pin run against origin/main's action-execution.ts (test file kept): 2 failed / 14 passed of the first 16 cases. Both failures were reader × hidden row and reader × missing id, at exactly two doors:

  "action MCP":  expected { code: PERMISSION_DENIED, status: 403 }  received { code: RECORD_NOT_FOUND, status: 404 }
  "action REST": expected { code: PERMISSION_DENIED, status: 403 }  received { code: RECORD_NOT_FOUND, status: 404 }
  "data MCP":    { code: PERMISSION_DENIED, status: 403 }   (as expected)

Every other cell, including editor × hidden/missing (404 at all doors) and both visible rows, already agreed. This is the card's table (filer's probe on c17719dd) on current main. The filer's probe script needs PR #30846's scenario harness worktree, so the re-measure went through this booted pin instead.

Ablation (restored: blob equals HEAD, git diff HEAD empty)
  • Mutation via scripts/ablation-replace.mjs: the verdict is consumed before the ask (refuseDeniedSubjectLoad(…); await refuseUngrantedDeclarativeUpdate(…)), i.e. the old order. The anchor went 1 → 0, and the blob went 97cc3fe3 → 7e1ef3a8. The suite imports runtime src/ directly, so no dist rebuild is needed.
  • Result: 4 failed / 16 passed. Reader × hidden and reader × missing are red in the matrix, and the reader's visible-vs-missing equality is red at action REST and action MCP. Every editor row and every data-door row stays green.
  • Restore: blob after restore 97cc3fe3 equals HEAD, and git diff HEAD is empty.
  • The first ablation attempt was a no-op and is not counted: its replacement still contained the anchor, so ablation-replace refused before running anything (anchor 1 → 1). The second attempt is the reading above.

Acceptance notes

  • The ask is object-level update with no payload, so two pre-lookup refusals the data door gives a caller who holds the update grant are not asked here: a field-level write refusal on a field the payload names (middleware step 2.5), and a read-only external datasource's write guard. For such a caller writing a hidden row or a missing id, the action door still answers 404 where the data door answers 403. Both answers stay the same for the hidden row and the missing id, so there is no oracle. This is not measured here, and the pinned matrix does not reach it.
  • A caller whose read of the object is refused but who holds edit: the data door answers the read refusal. The action door asks only update, so it keeps 404 for that caller. This is not measured here, and it needs a permission set granting edit without read.
  • explain denies a caller whose permission sets resolve empty. Since ADR-0056 D2 the middleware's CRUD gate does too, so this matches.

Verification

All on head 2dc4bdfa, except where a line names ad748b98. That earlier head differs only in docblocks and the changeset prose, plus one line removed from the new function: the isSystem short-circuit, dropped so that explain answers a system context as the middleware does.

  • pnpm --filter @objectstack/runtime typecheck: exit 0 (2dc4bdf).
  • Focused: the new pin plus action-declarative-update.test.ts, action-invocation-audit.test.ts and action-declarative-update-stale-summary.test.ts: 4 files, 90 tests passed (2dc4bdf).
  • @objectstack/runtime local project, whole suite: 550 files, 8801 passed, 19 skipped (ad748b9).
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 60 families, all run on 2dc4bdf with exit 0; --ran reconciliation: 60 derived, 60 run, 0 NOT-MEASURED. The first pass on ad748b9 had one red, check:system-context-census (a new isSystem read in a symbol no census row names), cleared by dropping that line. It also had one exit 3, check:dual-build-cjs-loads (PREREQUISITE NOT MET, no whole-tree dist), which exits 0 on 2dc4bdf.
  • pnpm lint: exit 0 (2dc4bdf).
  • Changeset: @objectstack/runtime patch, carrying both declaration lines.
  • Head 5f17e7c8 (one commit on 2dc4bdfa, test-only): the merge queue's Dogfood Regression Gate (3/3) caught showcase-anonymous-deny-surfaces.dogfood.test.ts pinning the old 404 for the no-edit member (run 42192137004). The case now asserts this PR's 403 PERMISSION_DENIED for the member, keeps Action-body writes have no not-found gate: ctx.api.object().update() against a nonexistent id answers 400 (or worse) instead of 404, while the protocol and callData paths both gate correctly #7867's ghost-id 404 with the dev administrator who holds the edit grant, and compares the action with the PATCH for both callers. Dogfood, full suite unsharded on 5f17e7c8: 423 files passed and 1 failed, 3537 tests passed and 2 failed. Both failures were catalog-activation-door.dogfood.test.ts :149 and :167 timing out at 5000 ms under box load; that file passed 10/10 alone on the same head, and the fixed file passed 80/80.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4czxVZYV4CcMiCbofw3

… 403 before the subject-load verdict, as the data door does

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czxVZYV4CcMiCbofw3
…oors on a booted kernel

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czxVZYV4CcMiCbofw3
… 403 ahead of the not-found pair

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czxVZYV4CcMiCbofw3
… bypasses it, so the census holds no new elevation read

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czxVZYV4CcMiCbofw3
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: PERMISSION_DENIED (literal, 51 pages)
  • the SDK route bridge reached 63 of 222 client-bound route-ledger rows — the other 159 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 159: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 104 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 98f640d581339769d54cf27c8f7b02cbc46849f4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 99b9207ab254a3cf7f5870c2cb9107b4b9555abd — the merge of head 5f17e7c8feabe06e769b7846af2e133d141eaa4a into base 98f640d581339769d54cf27c8f7b02cbc46849f4, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 99b9207ab254a3cf7f5870c2cb9107b4b9555abd && git checkout 99b9207ab254a3cf7f5870c2cb9107b4b9555abd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 98f640d581339769d54cf27c8f7b02cbc46849f4 5f17e7c8feabe06e769b7846af2e133d141eaa4a && git checkout -B drift-repro 98f640d581339769d54cf27c8f7b02cbc46849f4 && git merge --no-ff 5f17e7c8feabe06e769b7846af2e133d141eaa4a

node scripts/docs-audit/affected-docs.mjs --json 98f640d581339769d54cf27c8f7b02cbc46849f4

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

席位核对 · ACCEPT · 2026-11-06T09:02Z · #30848

Head-sha: 2dc4bdf

  • Shape: 3 files, +472/−0, four commits on origin/main a2f523aa. Inside claim 6679693194 (branch claude/project-thread-y8qx4d); not governed; no packages/spec/**, no plugin-security change.
    • packages/runtime/src/action-execution.ts: new refuseUngrantedDeclarativeUpdate, called in executeDeclarativeUpdateAction only when subject.recordLoadDenied, immediately before refuseDeniedSubjectLoad.
    • packages/runtime/src/declarative-update-refusal-order.pin.test.ts: the booted-kernel matrix.
    • One @objectstack/runtime patch changeset.
  • Review face: none of the four contract faces is touched (Clause-②: no, no narrowing declared, no published schema, no governed rule text), so this is the seat's own read, per contract-review.md.
  • Read in the diff:
    • The ask names no row: explain({ object, operation: 'update' }), the object-level verdict. So a no-edit caller's answer cannot depend on whether the id names a row. Visible row (the write's own 403), hidden row and missing id (this 403) all answer 403 PERMISSION_DENIED, and the pin holds them equal, sentence included.
    • An editor keeps one 404 RECORD_NOT_FOUND for a hidden row and a missing id: allowed returns before any refusal, and refuseDeniedSubjectLoad is unchanged. No existence oracle within a door (8 equality cases).
    • Fail-closed: an explain that throws is a denial, never read as a grant. No security service, or none with explain, refuses nothing, as at the data door.
    • The 403 sentence is the middleware's permission_denied through renderOperationMessage with the caller's locale and the deployment's i18n, so it equals the data door's (the visible-row equality cases).
    • The order matches http-protocol.mdx ("Refusals come before the record lookup") and actions.mdx ("exactly as for a user edit"), which the PR quotes.
    • Other refuseDeniedSubjectLoad callers are read admissions with no data door performing the same write; ActionSchema.operation is z.enum(['update']), so there is no declarative delete. Agreed that none shares the bug.
  • Contract: Clause-②: no is right. No request that succeeded is refused and no shape changes; one refusal's code moves from 404 to the 403 the documented contract and the data door already give. Metadata-protocol: none; patch level matches.
  • Note (non-blocking, not filed): an explain that throws because the authorization store is down now answers 403, where the data door answers that outage 503 SERVICE_UNAVAILABLE. Before this PR the same case answered 404, so nothing gets worse, and the path is reached only on a denied load.
  • Gates: every check-run on this head has concluded: 48 distinct checks, the latest run of each 35 success, 13 skipped, 0 failure. Locally (dev report 6680824105): the pin 20/20, baseline on main 2 red (reader × hidden, reader × missing, both action doors), ablation 4 red and restored; 60/60 derived families exit 0 with --ran 0 not measured; runtime typecheck and lint green; runtime suite 550 files green.
  • Landing: ready, then auto-merge, through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 09:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 6, 2026
…swers the write's 403; the edit-grant holder carries the ghost-id 404

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czxVZYV4CcMiCbofw3
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 42192137004 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Dogfood Regression Gate (3/3) — 失败步骤: Boot example apps and exercise real user flows

    @objectstack/dogfood:test:  FAIL   shared-showcase  test/showcase-anonymous-deny-surfaces.dogfood.test.ts > showcase: anonymous posture is uniform across surfaces (#2567) > [#7867] a member hitting th
      ↳ 失败原因: @objectstack/dogfood:test: AssertionError: a ghost record id must answer 404, not a 400 from further down the pipeline: expected 403 to be 404 // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • test/showcase-anonymous-deny-surfaces.dogfood.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 16 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions github-actions Bot added size/l and removed size/m labels Nov 6, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

席位核对 · ACCEPT (delta) · 2026-11-06T10:16Z · #30848

Head-sha: 5f17e7c (one commit on the accepted 2dc4bdfa; no merge, no rebase)

  • Why the delta: the merge queue (run 42192137004, Dogfood Regression Gate 3/3) caught packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts pinning the old answer: a member with no edit grant on showcase_task, calling the declarative update showcase_mark_done on a ghost id, was asserted 404. This PR's contract makes that 403 PERMISSION_DENIED, so the pin, not the code, was out of date.
  • Read in the delta (1 file, +47/−17, test-only):
  • No other pin: the dev grepped dogfood, verify, runtime, mcp, scenarios and examples for a no-edit declarative-update 404; none.
  • Gates: every check-run on this head has concluded: 49 distinct checks, the latest run of each 34 success, 15 skipped, 0 failure. Dogfood locally on 5f17e7c8, full suite unsharded: 423 files passed, 1 failed; the 2 failing cases were catalog-activation-door.dogfood.test.ts :149 and :167 timing out at 5000 ms under box load, which passed 10/10 alone on the same head; the fixed file passed 80/80. The merge queue re-runs the full dogfood gate.
  • Earlier ACCEPT 6681199251 stands for the rest of the diff, unchanged since 2dc4bdfa.
  • Landing: auto-merge re-armed through the relay.

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

Labels

documentation Improvements or additions to documentation size/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A declarative update action answers 404 RECORD_NOT_FOUND where the data update on the same row answers 403 PERMISSION_DENIED

2 participants