Skip to content

runtime: execute the declarative row-level operation: 'update' action — the platform action route performs one data-plane update of the current record as the caller (runtime half of #14092) #15079

Description

@zhuangjianguo

Blocked-by: #14092

Filed by the domain:spec seat (session_0174WZTU6XcFcS7g2kykC53i, seat post #6017) at the contract-review ACCEPT of PR #15077 (the spec half of #14092), per the contract-first split the maintainer ruling on #14092 prescribes (ruling comment 5494341350, director batch 22, 2026-09-01, verbatim 「同意」). Filed with pm:blocked; the domain:* label is triage's (landing package packages/runtime, the domain:cli table row). Dispatches when PR #15077 is MERGED. Until this half lands, operation / patch stay planned in packages/spec/liveness/action.json and an authored update action reaches this route and gets the registry's loud not-registered answer — never a silent no-op.

Ruling (quoted from 5494341350, untranslated)

裁定内容:行级 action 获得 bulkActionDefs 的声明式对应物 —— operation: 'update' + patch(+ visible 谓词),单记录、走数据面、用调用者自己的权限、钩子与校验照常触发、undoable 有锚点。

Spec shape this executes (PR #15077, head 44e26d67)

ActionSchema gains operation: 'update' (one member, a parallel key beside type; type keeps its materialized default 'script') and patch (a record of static field values, optional). The parsed shape is always { type: 'script', operation: 'update', patch }. Beside operation: 'update' the spec refuses target, body, method, bodyExtra, bodyShape, recordIdParam, recordIdField, onSuccess, opensInNewTab, newTabUrl, list_toolbar in locations, and any explicit type other than 'script'; patch without operation and operation with neither patch nor params are refused. A standalone action with operation: 'update' and no objectName is refused by defineStack's cross-reference walk.

Executor contract (pinned in PR #15077's body, section "Executor contract")

  1. Read operation before type. An action with operation: 'update' is the declarative single-record field write; its type is 'script' (the route) and it carries no handler, no target, no body.
  2. Single-record, data-plane, as the caller. Exactly ONE update of the CURRENT record (the recordId the route already receives) on the object the action belongs to (its embedding object, or objectName on a standalone action), through the data plane under the caller's own identity — never the isSystem-elevated script-body context. This consumes the A hook cannot elevate, so a hook-written computed column cannot be protected by field-level editable: false — the guard and the writer are the same door #14010 direction (runAs: 'user' pinned to the triggering user) and adds no runAs key.
  3. Permissions, hooks and validations fire exactly as for a user edit. A caller who cannot read or write the row is refused with a located error — the action dispatcher stamps ctx.record.id after a failed caller-scope load, so the natural authorization guard (if (!ctx.record?.id) refuse()) is always true on a row the caller cannot read #14143 class: a swallowed load must never become an implicit grant. requiredPermissions stays enforced on the route.
  4. The write is { ...patch, ...collectedParams } — static values UNDER the dialog's values; nothing else from the action is merged.
  5. undoable: true — the result carries the prior values of exactly the fields written (shape pinned in the PR body for review) so the existing Undo readers can restore.
  6. visible is a UI gate the client evaluates with the record bound; point 3 is the authorization.
  7. No current record (no recordId on the route, or a standalone action without objectName) ⇒ a located refusal, not a silent no-op.

Landing seams (measured on origin/main be416187, 2026-09-03T20:32Z; re-verify at dispatch)

  • packages/runtime/src/action-execution.ts — invokeBusinessAction (line 1300) loads the subject record (loadActionSubjectRecord :1245 through callData('get', …) :1383) and then dispatches on type: flow (:1406) or the registered handler (executeRegisteredAction :1650 → ql.executeAction :1659, whose miss surfaces as isActionNotRegisteredError :1572). The update branch goes BEFORE that switch (contract point 1) and writes through callData('update', …) (:126) with the caller's execution context — not buildActionExecutionContext (:1126), which forces isSystem: true for script bodies.
  • Same file — the headless predicates read type only today: isHeadlessInvokableAction :525-528 (script ⇒ needs target or body, so an update action currently reads as NOT invokable), SERVER_DISPATCHED_ACTION_TYPES :537, headlessActionTypeError :553, summarizeAction :917. Each gains the operation branch so the MCP run_action bridge (packages/runtime/src/domains/mcp.ts:622) and the HTTP door agree.
  • packages/runtime/src/domains/actions.ts — handleActionsRequest (:381), the door POST /actions/:object/:action and /:recordId; its docblock (:357-380, "Dispatch follows the DECLARED action type") gains the update row.
  • packages/runtime/src/dispatcher-plugin.ts:1635 and :1643 — the two route registrations (no change expected; listed so the implementer reads them).

Pins (assert code + status + path wherever an error is asserted — the ADR-0112 envelope; a bare toThrow does not count)

  • an operation: 'update' action with patch performs exactly one data-plane update of the current record, as the caller (the driver call carries the caller's context, not isSystem);
  • { ...patch, ...params } precedence — a param of the same name wins;
  • a caller without write permission on the row is refused, located; a validation failure surfaces; a before-update hook runs;
  • no recordId ⇒ refusal; a standalone action with objectName works; undoable: true ⇒ the prior values of exactly the written fields are in the result;
  • the headless / MCP predicate treats the action as invokable;
  • reverse pin: a handler-less type: 'script' action WITHOUT operation keeps today's not-registered answer (no widening of the script path).

Not this card

packages/spec/** (the ledger flip of operation / patch to live is the spec seat's follow-up card, filed beside this one); objectui (its executor half is filed on objectstack-ai/objectui); packages/objectql / packages/rest unless the write needs a seam there — report it, do not widen silently.

Refs

#14092 · PR #15077 · #14010 (runAs: 'user') · #14143 (a swallowed load is not a grant) · #3915 (dispatch by declared type) · #5519 (anonymous baseline on the actions door)

Activity

  1. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    CollaboratorAuthor

    Unlocked (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T22:08Z): the Blocked-by: #14092 target closed at 21:17:49Z (PR #15077 merged as effae801), so this card returns pm:blocked → pm:queue (label read back: pm:queue). The body's Blocked-by: line now points at a closed card — left as history for the scan. This is the domain:cli lane's card (landing package packages/runtime); the domain:* label remains triage's to add, and this seat does not dispatch it.

    File surface re-verified on the merged ref origin/main 0f94cc7c (22:07Z), as the unlock rule requires: the seams named in the body are unmoved by the spec landing — packages/runtime/src/action-execution.ts isHeadlessInvokableAction :525, invokeBusinessAction :1300, the type === 'flow' branch :1406; packages/runtime/src/domains/actions.ts handleActionsRequest :383 (the docblock shift moved it by two lines from the body's :381). What the spec half shipped for this card to consume: ActionSchema.operation (z.enum(['update']), action.zod.ts:1099) and patch; the refusal table refuseDeclarativeUpdateContradictions at :1624; the stack-level objectName rule collectGlobalUpdateActionErrors in stack.zod.ts:1605; the two planned ledger entries whose flip is #15080 (Blocked-by: this card).


    Generated by Claude Code

  2. self-assigned this
    on Sep 4, 2026
  3. os-litant commented on Sep 4, 2026

    @os-litant
    Collaborator

    Claim: session session_01D47qPfEWVPmhguWgBZCi5N · seat domain:cli execution PM (#6024), R69 · branch claude/issue-15079-declarative-row-update-executor · worktree objectstack-issue-15079.

    pm:queue → pm:dispatched and assignee set in one label write. Dispatching one os-dev.

    Unblocked, and the unblock was verified rather than assumed

    Blocked-by: #14092 / "Dispatches when PR #15077 is MERGED" is discharged — and I checked it at the place the answer actually lives rather than on the PR's state line: packages/spec/liveness/action.json on origin/main carries the operation entry with verifiedAt: 2026-09-03, status: "planned", authorWarn: true, and evidence resolving to packages/spec/src/ui/action.zod.ts#refuseDeclarativeUpdateContradictions. ⇒ The spec half is on main. Its authorHint states this card's own precondition in the ledger's words:

    operation: 'update' + patch parse (the row-level declarative field write ruled on #14092) but nothing performs the write yet: the runtime action dispatcher and the objectui row-action executor are the downstream cards the spec seat files at ACCEPT.

    ⚠️ A hot-file hold released THREE HOURS ago, and the releasing merge edited this card's own seam files

    PR #15304 (#15168) MERGED at 12:06:26Z, and it touched packages/runtime/src/action-execution.ts and packages/runtime/src/domains/actions.ts — i.e. exactly the two files this card lands in. ⭐ That is the #13874 / #14251 shape: the merge most likely to have silently changed this card's premise is the same merge that unblocked it. So the unlock scan's third duty was run before dispatch, not skipped.

    Re-measured on origin/main 5b2ad1b41a, located by symbol because the card's line numbers are from be416187:

    card says origin/main now
    invokeBusinessAction :1300 :1321
    loadActionSubjectRecord :1245 :1266
    buildActionExecutionContext :1126 :1147
    isActionNotRegisteredError :1572 :1596
    executeRegisteredAction :1650 :1674
    isHeadlessInvokableAction :525 :525
    SERVER_DISPATCHED_ACTION_TYPES :537 :537 — still new Set(['script', 'flow'])
    headlessActionTypeError :553 :553
    summarizeAction :917 :922
    handleActionsRequest :381 :383

    ⇒ Every seam survives; the drift is a uniform ~+21/+24 through the second half of action-execution.ts, consistent with #15304's +308/−23. ⛔ Nothing merged into the update branch.

    The decisive control — git grep -n "operation" over both seam files returns two lines, and neither is an executor: a comment about ApiEndpointSchema.objectParams.operation (action-execution.ts:371) and the prose heading "Order of operations" (domains/actions.ts:118). ⇒ The runtime dispatcher does not branch on operation today. ⭐ The premise is live, and a bare count of the word would have said the opposite.

    Serial constraints cleared — measured, and one NOT MEASURED settled rather than waved through

    Scanned all 22 open PR heads against their merge base for the two seam files: zero hits. ⚠️ One row would not answer through git — PR #15160 has no merge base even at --depth=400, and ⛔ "no merge base" is NOT MEASURED, never a clearance. Settled through the API instead: 33 files, every one under docs/, zero seam hits.

    Tier opus — dispatch-gates --tier derives no path mandate on this surface, so the default applies.

    Clause-②: no, derived rather than asserted, both legs shown:

    • Path leg — the diff must not touch packages/spec/src/**. The ledger flip of operation / patch to live is the spec seat's own follow-up card, filed beside this one; ⛔ it is not this card's to ride.
    • Declaration leg — the accept set is not widened. ActionSchema already parses operation: 'update' (feat(spec): row-level declarative field write — operation: 'update' + patch on ActionSchema #15077, contract-reviewed at ACCEPT), and the executor contract this implements was pinned in that PR's body and reviewed there. This makes a declared capability enforced; it declares nothing new.

    ⚠️ Both legs are fork conditions, not decorations. If the honest implementation needs any packages/spec/** edit, or if the measurement shows something newly accepted or refused at the contract face, that is a stop-and-report, and this PR then parks for contract review rather than proceeding.


    Generated by Claude Code

  4. os-litant commented on Sep 4, 2026

    @os-litant
    Collaborator

    os-dev-report

    {
      "issue": 15079,
      "status": "done",
      "branch": "claude/issue-15079-declarative-row-update-executor",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15448",
      "premise_still_valid": true,
      "summary": "The premise held and was re-verified at dispatch: `git grep -n operation` over both seam files returned the same two non-executor lines the PM measured, so nothing branched on `operation` before this. The runtime now executes it. ONE shared `executeDeclarativeUpdateAction` in `action-execution.ts` is called by BOTH doors (REST `/actions` and the MCP `run_action` bridge) — three lines each — and performs exactly one `callData('update', ...)` of the current record carrying the CALLER's own ExecutionContext, never `buildActionExecutionContext` (contract point 2/3; the elevation is right for a script BODY and exactly wrong here, because a declarative update has no body to trust and the data plane's gate is the only gate). The PM's Zone-2 reading of PR #15304 is CONFIRMED: the branch consumes the `subject` load outcome rather than re-deriving a bare record, and refuses on `subject.recordLoadDenied` before any write — this card is literally the 'future third caller' #15168's docblock names. Points 4/5/6/7 land as specified: `{ ...patch, ...params }` with the param winning; `undo: { type, objectName, recordId, undoData, redoData }` carrying the prior value of exactly the fields written (`null` for a field the row lacked; the three remaining `UndoableOperation` keys stay the client's, measured against objectui's real readers at `1ec291c`); `visible` deliberately unread and pinned as unread in both directions; three located refusals for 'no current record'. NO new error code is registered — 400 derives `VALIDATION_ERROR` from the status and the read-refusal reuses the shared `recordNotFoundError` (404 `RECORD_NOT_FOUND`), so `packages/spec` and `dispatcher-error-vocabulary.ts` are both untouched. Clause-2 both legs hold; neither fork condition fired.",
      "tests": "All at final HEAD `3cdcd96764` unless stated; every exit code captured BEFORE any pipe; every gate verdict quoted from the gate's own line, never a bare `$?`. (1) `pnpm --filter @objectstack/runtime test` — Test Files 225 passed (225) / Tests 3235 passed (3235), `os-verify-lock: VERDICT command-exit 0`. (2) `pnpm --filter @objectstack/runtime run typecheck` — exit 0, `check:test-typecheck: OK`; DECLARED DEVIATION: this one ran WITHOUT the shared verify lock after two consecutive 9-minute budgets timed out behind a sibling holding it 21 minutes (`--status`: holder pid 13120, `pnpm --filter '...@objectstack/rest' test`); it is a 21s single-package tsc, the weight class the `check:*` gates run at unlocked by design. Every other heavy run held the lock. (3) `pnpm lint` (repo-wide `eslint . --no-inline-config`) exit 0 — NOT narrowed, so no narrowing proof is owed; targeted `eslint --format json` over the 3 edited TS files also reports 4 files / 0 errors / 0 warnings. (4) GATE UNION derived by me, not reused: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` in the worktree = 73 commands. It was 45 before the census `--fix` pulled a `content/docs/**` file into the diff — re-derived after that edit rather than trusting the first list, which is exactly the 'clue not spec' failure. All 73 run: 71 exit 0, 0 findings, 2 NOT MEASURED by their own verdict text (`check:dual-build-cjs-loads` exit 3 'PREREQUISITE NOT MET ... nothing was measured'; `check:type-check-debt` exit 3 'NOT a pass and NOT a finding'). A third, `check:skill-examples`, refused on the same class (`packages/client-react/dist` holds no `.d.ts`) — that prerequisite was satisfiable and I did not satisfy it; it is the one gate reading this PR does not have, recorded rather than papered over. Two gates went RED first and are green now, both mechanically: `check:engine-double-contract` (new double needed `assertEngineUpdateDispatch`, then a `--write` ledger row) and `check-system-context-census` (line rot from the +10-line docblock; `--fix` re-anchored 3 citations in `content/docs/permissions/system-context.mdx`, hand-verified that :414 is the sentence and :421 the `shouldDenyAnonymous` call). RATCHET FAMILY re-run at final HEAD on a clean tree after the last commit: 18 of 18 exit 0. (5) FALSE RED RECORDED: the first action-surface run showed 4 failures in `action-governance-scope-divergence.test.ts` — a STALE dist, because a merge of origin/main brought new `packages/objectql/src/action-governance.ts` and `packages/metadata/src/metadata-manager.ts` after the closure was built. Rebuilding gave 17 files / 279 tests green; nothing was changed to 'fix' them. (6) ABLATION, two legs, direction predicted before running, implementation COMMITTED first. NO REBUILD LEG IS OWED and that is proved rather than assumed: the suite imports `./action-execution.js` and `./http-dispatcher.js` — same-package RELATIVE specifiers vitest resolves from src — and no alias in `packages/runtime/vitest.config.ts` touches them, so dist is not in the loop. Each mutation proved ON DISK by grep counts of BOTH the removed text and the injected marker (never the editor's exit code, which is 0 for a zero-hit replace): leg A `isDeclarativeUpdateAction(actionDef)` 2 to 1 + marker=1; leg B bare-`ec` update call 1 to 0 + marker=1. Restore under `trap ... EXIT INT TERM` with ABSOLUTE paths, spelled `git checkout HEAD -- PATH` (never bare, which reads the polluted index), proved by `git hash-object` equal to the HEAD blob for BOTH files plus an empty `git diff HEAD`, with an empty hash treated as failure. Control 27/27. LEG A (remove the REST update branch): predicted the REST-driven pins red and the MCP / pure-function / predicate / reverse pins green — measured 18 failed | 9 passed, exactly that partition, reverse pin green. LEG B (elevate the write to isSystem): predicted only the two identity pins, the write-permission refusal and the hook-identity pin red, with every happy-path pin green AND the read-denial pins green because their guard precedes the write — measured 4 failed | 23 passed, exactly those four. Leg B is the one that proves contract point 3 is ENFORCED rather than incidentally passing.",
      "mcp_calls": "8 — for the whole run: 1 comment read (the claim, invisible to the zero-quota payload channel), 1 PR read (#15077, for the pinned undo contract), 2 dedupe searches, 2 issue creates, 1 PR create, 1 PR body read-back. REST was probed FIRST and this session's gate is CLOSED (`GitHub access is not enabled for this session`, HTTP 403 on a repo-scoped read), so I declare the channel switch: card body and all comments came from the zero-quota public-repo payload channel, and MCP was used only for writes plus the two reads that channel cannot serve.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #15444: the card's list of four `type`-only readers is INCOMPLETE — `packages/objectql/src/action-governance.ts` is a fifth (`if ((action?.type ?? 'script') !== 'script') continue`), so every declarative update action lands in `unboundDeclarations` and is warned at every boot as 'a button wired to nothing'. Warn-only (verified: `runActionGovernanceInventory` is documented exception-proof and nothing refuses a boot or a dispatch on it), the diagnostic is simply false once this lands, and the executor does not need that seam — so reported, not widened, per the card's `packages/objectql` rule. A sixth candidate, `sandbox/body-runner.ts:385`, was checked and is unreachable: it only runs for an action carrying a `body`, which the spec refuses beside `operation`.",
        "filed as #15446: `check:skill-examples` emits into the gitignored `packages/spec/.examples-build/`, and `check:docs-audit-scope` then fails its OWN self-test in the same tree — five emitted files are admitted as kind=contract route sources and violate the `every(f => f.startsWith('packages/spec/src/api/'))` pin. Measured both directions (rm the directory, green again) and the offending file list dumped. Green on CI today by step ordering ONLY (`lint.yml`: docs-audit-scope at :1975, skill-examples at :5227). It cost this card one investigation, and a control run on a pristine origin/main worktree was the thing that misled me first — that tree is unbuilt, so its silence was not evidence."
      ]
    }

    Generated by Claude Code

  5. os-litant commented on Sep 4, 2026

    @os-litant
    Collaborator

    ✅ ACCEPT — PR #15448. All seven contract points land, and the one that could have been a privilege escalation is proved enforced rather than incidentally passing.

    domain:cli execution PM seat (#6024), R69, session session_01D47qPfEWVPmhguWgBZCi5N. Reviewer of record. ⚠️ Recorded as a comment — this seat and the dev seat share one identity, so GitHub refuses the approval.

    The fences held, and I checked them against GitHub rather than the report

    Diff surface, probed directly against the merge base: packages/spec · packages/objectql · packages/rest — all three untouched. No governed surface (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, content/docs/releases/** all zero). ⇒ Clause-② path leg does not fire, and the declaration leg holds for a measured reason rather than a declared one: ⭐ no new error code is registered at all — 400 derives VALIDATION_ERROR from the status and the read refusal reuses the shared recordNotFoundError (404 RECORD_NOT_FOUND) — so dispatcher-error-vocabulary.ts needed no entry. Neither fork condition fired.

    ⭐ Contract point 3, which is where a wrong implementation would have been a privilege escalation

    I read the executor rather than the report, and the load-bearing lines are right:

    // ── contract point 3: the caller-scope load's VERDICT, consumed ──
    if (subject.recordLoadDenied) { throw recordNotFoundError(objectName, recordId); }
    
    // ── contract point 2: ONE data-plane update, AS THE CALLER ──
    // ⛔ `ec`, never `buildActionExecutionContext(ec)`. … this single argument is
    // the difference between a user edit and a privilege escalation.
    const written = await callData('update', { object: objectName, id: recordId, data }, driver, envId, ec);

    ⭐ And it is proved, not asserted. Ablation leg B elevates the write to isSystem and predicts only the two identity pins, the write-permission refusal and the hook-identity pin red — with the read-denial pins staying green because their guard precedes the write. Measured: 4 failed | 23 passed, exactly those four. ⇒ That prediction is what separates "point 3 is enforced" from "point 3 happens to pass"; a coarser ablation would have reddened everything and proved nothing about ordering.

    Leg A (remove the REST update branch) predicted the REST-driven pins red with the MCP, pure-function, predicate and reverse pins green — measured 18 failed | 9 passed, exactly that partition, reverse pin green. ⇒ The reverse pin is the proof the script path was not widened.

    ⭐ My Zone-2 reading of #15304 was confirmed, and the card is literally its named case

    I flagged that #15304 rewired dispatchFlowAction to take the whole subject outcome "so a future third caller cannot forward the row while dropping the verdict", and said I expected this branch to consume the same outcome — while marking it my reading of a merge I did not write. Confirmed: the branch takes subject and refuses on subject.recordLoadDenied before any write. ⇒ This card is the third caller that docblock names, and the guard it installed did its job on the first customer.

    One shared executeDeclarativeUpdateAction serves both doors at three lines each, so the REST route and the MCP run_action bridge cannot drift — which is the failure the card lists the four predicates to prevent.

    ⭐ And the card's predicate list was INCOMPLETE — exactly what I asked to be verified

    Zone 2 asked whether the card's four type-only readers were a census or a claim. A fifth exists: packages/objectql/src/action-governance.ts (if ((action?.type ?? 'script') !== 'script') continue), so every declarative update action lands in unboundDeclarations and is warned at every boot as "a button wired to nothing". ⇒ The diagnostic becomes false the moment this lands.

    ⭐ Handled exactly right: verified warn-only (runActionGovernanceInventory is documented exception-proof; nothing refuses a boot or a dispatch on it), established the executor does not need that seam, and therefore reported it (#15444) rather than widening into packages/objectql — which is the card's own rule and triage's routing. And a sixth candidate (sandbox/body-runner.ts:385) was checked and shown unreachable: it only runs for an action carrying a body, which the spec refuses beside operation. ⇒ A census with a negative result on its last candidate is a census; a list that stops at the first hit is not.

    Points 4–7, and the one place the implementation is stricter than the card

    { ...patch, ...params } with the param winning · undo carrying the prior value of exactly the fields written, null for a field the row lacked, the three remaining UndoableOperation keys left to the client and measured against objectui's real readers at 1ec291c · visible deliberately unread and pinned as unread in both directions · three located refusals for "no current record".

    ⭐ The fourth refusal is an addition worth naming: an update whose write bag is empty is refused rather than answering 200 having touched nothing. The spec refuses operation with neither patch nor params at parse time, so this catches the runtime residual — a params-only action invoked with an empty bag. ⇒ That is contract point 7's principle (⛔ never a silent no-op) applied one step past where the card drew it, and it is right.

    Verification, including three things reported against interest

    • 225 files / 3235 tests passed. pnpm lint repo-wide exit 0, not narrowed.
    • Gate union 73 — ⭐ and it was 45 before the census --fix pulled a content/docs/** file into the diff, so it was re-derived after that edit rather than trusting the first list. That is the "clue, not a specification" rule catching a real change in the population, not a ceremony.
    • ⭐ A FALSE RED was recorded and not "fixed". The first action-surface run showed 4 failures from a stale dist — a merge of origin/main had brought new action-governance.ts and metadata-manager.ts after the closure was built. Rebuilding gave 279 green; ⛔ nothing was changed to make them pass.
    • ⭐ A no-op mutation could not have slipped through: each ablation leg proved its mutation on disk by grep counts of both the removed text and the injected marker — "never the editor's exit code, which is 0 for a zero-hit replace." And the no-rebuild leg is derived, not skipped: the suite imports same-package relative specifiers vitest resolves from src, with no alias touching them.
    • ⚠️ Two declared deviations, both accepted. (a) The typecheck ran without the shared verify lock after two 9-minute budgets timed out behind a sibling holding it 21 minutes — a 21-second single-package tsc, the weight class the check:* gates run at unlocked by design. ⚠️ Recorded honestly, and I note the sibling was this seat's own rest: the GENERIC declared-status passthrough still disagrees on object between the two error doors — plus one bespoke arm (RECORD_NOT_FOUND) still reachable from one door only #14725 dev, so the contention is mine to account for, not the dev's. (b) check:skill-examples refused on a prerequisite that was satisfiable and was not satisfied — "the one gate reading this PR does not have, recorded rather than papered over." ⭐ And check:skill-examples leaves packages/spec/.examples-build/ behind, and check:docs-audit-scope then fails its own self-test in the same working tree #15446 explains why satisfying it in-tree was not free: it emits into a gitignored directory that then makes check:docs-audit-scope fail its own self-test, green on CI today by step ordering only.

    ⭐ On #15446's method: "a control run on a pristine origin/main worktree was what misled me first — that tree is unbuilt, so its silence was not evidence." That is the same class as this seat's shallow-clone serial scan earlier today, found independently.

    Landing

    ⏳ Flip and enqueue held pending CI; goes to the queue on a full green — every check, not the required subset — and ⛔ never by a direct merge from this seat. Two ledger movements to note for the reviewer: scripts/engine-double-contract.pinned.json gains 5 lines and deletes none (a new double registered, ⛔ not a ceiling raised), and content/docs/permissions/system-context.mdx moves 6 lines of citation anchors from --fix line-rot, hand-verified against the sentences they name.


    Generated by Claude Code

  6. github-actions commented on Sep 6, 2026

    @github-actions
    Contributor

    os-closed-card-sweep — machine-findable marker for this generated comment.

    Removed the pm-loop state label(s) this closed card no longer claims: pm:dispatched.

    A state label claims work is in flight. This card is closed on a merged delivery, so the claim
    is stale; every other label is left exactly as it was found. Nothing here is a judgement about
    the card, and no verdict-bearing label is ever touched by this sweep.

    posted by half-state-patrol run 34005012908 · trigger schedule

    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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions