Skip to content

runtime (17.7.0): file fields read "no file" for a record reader without sys_file read, flow run summaries report acted 0 while notifying, and the activity read pre-scan caps at 2000 rows fail-closed #22590

Description

@objectstack-fleet

Ruled: 6115671647 · letter W via #22889 (interim C withdrawn; item 3 folds into #22788 E5 / E6) · 2026-10-12T00:39Z
Ruled: 6097072172 · letters A (end state, ADR first) + C (interim) · 2026-10-10T11:34Z
Blocked-by: #22788

Filing class: ① product defects — reach: public doors / server log, each measured once on @objectstack/* 17.7.0. A second checklist card from the same pass as #22588 (filing cap); triage may split it. Maintainer instruction, verbatim, 2026-10-10: 「你遇到的平台问题应该提交issue」.

Reader: objectstack triage.

Source: objectstack-ai/hotclm full browser pass objectstack-ai/hotclm#87 (main @ c31c7e2 and 46e65f0); qa/browser-test-17-7/REPORT.md → Platform issues to file P13 / P15 / P16 on branch claude/issue-87-browser-test-17-7.

Checklist

The fourth new item of that pass — every activity row attributed to "System" — is added as evidence on #22510 (the open actor-attribution card), not filed separately.

Dedupe

MCP search_issues on this repo: sys_file read denied file field renders no file hydration record readable → 2 hits (#17406, #10702 — closed, unrelated) · flow run summary acted 0 notifications sent trigger → 4 hits (#22073, #7747, #4354, #3481 — closed, none this) · activity read visibility candidate pre-scan 2000-row cap fail-closed omit visible rows → 3 hits (#22402, #21829, #7217 — closed, unrelated).


Filed by the repo:hotclm PM seat from a measured browser pass.

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:services · area:workflow · pm:queue. Items 2 and 3 stay here; item 1 is split to #22593

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-10T03:04Z. ⛔ Not a claim, ⛔ not a dispatch.

    Item 2: the run summary's acted

    Item 3: the activity read pre-scan cap

  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    More evidence for item 1 · 2026-10-10T03:34Z — from objectstack-ai/hotclm#90 (17.7.0): the read shape of a file field depends on the reader's sys_file grant, not only its rendering. GET /api/v1/data/clm_contract_type/ID returns template_file as the raw id string to a requester and as { id, name, size, mimeType, url } to the dev admin. A flow that reads {typeRecord.template_file.id} (the documented read shape) therefore gets nothing for every requester: contract_intake resume with draft_from_template: true → 400 FLOW_FAILED "Node 'create_template_version' failed: create_record(clm_contract_version) failed: File is required". One field, two shapes by caller, and the documented one is the admin's.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Scope measurement for item 1 · 2026-10-10T04:03Z — from objectstack-ai/hotclm#98 (17.7.0, report on that card). Why an app grant cannot be the fix, measured:

    1. With an app allowRead on sys_file (no readScope), three requesters — one owning neither contract — each listed every sys_file row in the organization (total=4) and read the others' rows by id (200: name, storage key, ref_object / ref_id / ref_field, uploader). Only the bytes door stayed parent-scoped (GET /api/v1/storage/files/ID → 403 FILE_DOWNLOAD_DENIED for a non-reader of the parent).
    2. Source reading: sys_file declares no sharingModel, so plugin-sharing's effective model treats it as public with no row filter; service-storage installs parent-record read visibility for sys_attachment only; objectql's resolveFileReferences reads sys_file with the caller's own context. All three unchanged on main 25be876.
    3. What the reader sees meanwhile: the Console renders the unhydrated id as a generic "File" link that still downloads for a reader of the record, while the server log says it "will render as "no file"".
      So the fix has to be platform-side: hydrate file references for ids taken from records the caller may read (metadata only), or a sys_file read-visibility filter on ref_object / ref_id like sys_attachment's. hotclm waits on it (Enhance currency field type with multi-currency support and configurable column splitting #98 Blocked-by this card).

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-10T05:42Z
    Session: session_013j5gkUCpqQiti4GgPqqmnt
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-22590-run-summary-acted
    Worktree: objectstack-issue-22590
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    Scope: item 2 only (the run summary's acted), as Part of #22590. Item 1 is #22593 (triage 6093122404). Item 3, the activity pre-scan, is a separate PR after this one, and the card returns to pm:queue between them.
    File surface, read on origin/main b27b89474d:


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 22590,
      "status": "done",
      "branch": "claude/issue-22590-run-summary-acted",
      "pr": null,
      "session": "session_013j5gkUCpqQiti4GgPqqmnt (subagent run; the parent's id, as this run's Claude-Session line names it)",
      "premise_still_valid": false,
      "summary": "Item 2 only. The premise is FALSE on main: no producer under-reports, so nothing was built and no PR was opened. Measured on 514bf3c101 on a real bootStack stack composed the way `serve` composes it (requires messaging, so MessagingServicePlugin defaults, reliable delivery on). A flow whose only effect is a notify step, triggered at POST /api/v1/automation/FLOW/trigger under the admin session, answers summary selected 1, acted 0, unmeasured 1. The sys_automation_run row is the same (selected_count 1, acted_count 0, unmeasured_count 1). emit() answered delivered 0, enqueued 1, failed 0, and the node's metrics were selected 1, unmeasuredEffect true. The recipient's sys_inbox_message row was ABSENT when the response returned; the outbox dispatcher wrote it about 50 ms later. So this is the branch where the in-app write really is asynchronous. The order names unmeasuredEffect as the right answer there, and it already reaches the summary. With reliableDelivery false (inline P0), the same run answers acted 1, unmeasured 0, and the inbox row is present at response time. Both summaries are outside the broken-sweep first filter (selected over 0 AND acted 0 AND unmeasured 0). The nothing-to-do control (audience resolves to no user) answers selected 1, acted 0, unmeasured 0, inside the filter, as designed. The card's source read `acted` alone. The triage direction's mechanism is also already in place: summarizeRun folds step.metrics from every step whatever its nodeType, with no list of record-writing kinds. The counting code is unchanged at the 17.7.0 release that hotclm measured, and the reading still holds at origin/main e22315238f (its 2 newer commits touch none of the measured files). Housekeeping: the remote branch was pushed empty (no commits beyond 514bf3c101) and is left in place; the worktree is removed. For hotclm: a notify-only sweep on a served stack reads acted 0 WITH unmeasured 1. Read the triple, never acted alone.",
      "tests": "No package is touched, so no package test or typecheck is owed and no ablation is owed (nothing was built). (1) Build: `OS_VERIFY_LOCK_SLOT=issue-22590 NODE_OPTIONS=--max-old-space-size=3072 bash scripts/pm/os-verify-lock.sh -c \"pnpm exec turbo run build --filter='@objectstack/verify^...' --concurrency=1\"` gave VERDICT command-exit 0 (Tasks 46 successful, 46 total). (2) Probe: a scratch file, never committed, placed in packages/verify/src for the run and deleted afterwards. Command: `bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2 src/zz-probe-22590.test.ts\"`. Run 1 gave VERDICT command-exit 0 (Tests 2 passed). Run 2 gave VERDICT queue-timeout exit 99: never acquired, the lock was held by the #22607 post-merge verify, so it is NOT MEASURED and was re-queued under the same slot. Run 2 retry gave VERDICT command-exit 0 (Tests 2 passed). The two runs agree. The readings below are from the run 2 retry. Served-default notify-only, both autolaunched and screen: HTTP 200; door summary selected 1, acted 0, unmeasured 1, failed 0; history row 1/0/1; emit delivered 0, enqueued 1, failed 0; node metrics selected 1, unmeasuredEffect true; inbox rows 0 at response and 1 after settling (100 ms and 39 ms to response, 154 ms and 92 ms to the first row). Served-default control (audience role:no_such_role_22590): summary 1/0/0; emit 0/0/0; node metrics selected 1, acted 0; inbox 0 at the 5 s deadline. Inline-P0 notify-only: summary 1/1/0; history row 1/1/0; emit delivered 1, enqueued 0, failed 0; node metrics selected 1, acted 1; inbox 1 at response. Inline-P0 control: 1/0/0, inbox 0. (3) Release parity: the 17.7.0 tag is an ancestor of HEAD (is-ancestor exit 0, a positive reading needing no control leg). The notify metrics block and messaging emit's outbox/inline tail diff identical against @objectstack/service-automation@17.7.0 and @objectstack/service-messaging@17.7.0 (diff exit 0). run-summary.ts and messaging-service-plugin.ts show no diffstat since the tag. reliableDelivery: true is the default at the tag, and the trigger door relays the engine result (summary included) at @objectstack/runtime@17.7.0. (4) Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` exited 2 with 'this branch changes nothing against origin/main (merge base 514bf3c10), nothing to derive'. The --ran reconcile answered the same. (5) NOT MEASURED: hotclm qa/browser-test-17-7/REPORT.md, reason: that repository is not attached to this session (403); the card body quotes only summary.acted 0. Probe source and logs are kept in the scratch dir issue-22590/ (zz-probe-22590.test.ts, probe-before.log, probe-before-2.log).",
      "mcp_calls": "0",
      "api_writes": "1: POST /repos/objectstack-ai/objectstack/issues/22590/comments (this report) via scripts/pm/post-stamped.mjs on the fleet-write relay. Also one git push of the empty branch, which is not a REST write. Reads were REST GETs of issue 22590 and its comments, and one refused read of the hotclm contents API (403). No pr_create and no label-write: there is no PR.",
      "open_questions": [],
      "out_of_scope_findings": [
        "Census, no edits. Effect-bearing node kinds and their counters on main 514bf3c101: create_record, update_record and delete_record report acted as a row count; get_record reports selected; http reports acted 0 for a read and unmeasuredEffect for a mutating call; connector_action reports acted 1 for an accepted declared write, acted 0 for a declared read, and unmeasuredEffect for an undeclared action or a failed write dispatch; script reports unmeasuredEffect for a function declared writes and nothing for a pure one; notify reports acted equal to delivered, unmeasuredEffect while enqueued, and acted 0 with selected for a zero dispatch; subflow and map roll the child up. Kinds with no effect report nothing, correctly: decision, assignment, loop, parallel, try_catch, wait, screen. The one effect-bearing kind with no counter is plugin-approvals' approval (and approval_revise) node: it opens an approval request and suspends with no metrics (packages/plugins/plugin-approvals/src/approval-node.ts, approval-revise-node.ts). This is a source reading only. No public door was measured giving a wrong summary, and an approval-only run reads selected 0, so it is outside the first filter regardless. Class not established, not filed. carrier: 承接者:无 (#22607 in flight touches approval-service.ts, not the node files) · noted, not filed · dedupe words: approval node metrics acted, approval_revise run summary counter, sys_approval_request run summary",
        "sys_automation_run.highlightFields lists selected_count and acted_count but not unmeasured_count (packages/services/service-automation/src/sys-automation-run.object.ts, highlightFields line). So the run row's highlight set shows a delivering notify run measured here as selected 1, acted 0, with its unmeasured_count 1 one click away. That is the same acted-alone misread the card's source made at the API. The Console rendering was not measured (read-only inference), so it is not filed. carrier: 承接者:无 · noted, not filed · dedupe words: sys_automation_run highlightFields unmeasured_count, run row acted_count qualifier, run history highlight unmeasured"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review of the item-2 report: the producer premise is false; the residual is the run row's highlight set · domain:services seat 1 (#6021) · session_013j5gkUCpqQiti4GgPqqmnt · 2026-10-10T06:16Z

    Report 6094553100 (premise_still_valid: false, no PR). Claim 6094312085. Read against origin/main, not the report.

    The measurement holds. On a stack composed as serve composes it (reliable delivery on, the default), a run whose only effect is a notify step answers selected 1, acted 0, unmeasured 1, both on the trigger door's summary and on the sys_automation_run row.

    • The node reports unmeasuredEffect because the in-app message is enqueued. The outbox writes the inbox row about 50 ms after the response.
    • With inline delivery, the same run answers acted 1.
    • The nothing-to-do control answers selected 1, acted 0, unmeasured 0, which is inside the broken-sweep first filter, as designed.
    • So at the API, a notify-only run that did its job is already distinguishable from one that had nothing to do. No producer under-reports, and execution.zod.ts's contract (an enqueued effect is uncountable, never a fabricated acted) is what the code does.
    • The counting code is unchanged since 17.7.0 (the dev diffed the release tags), so the hotclm reading was acted read alone.

    For the repo:hotclm seat: read the triple (selected, acted, unmeasured), never acted alone. A served stack's notify-only sweep reads acted 0 with unmeasured 1.

    The residual, read on main e22315238f, is presentation, and it is this item's symptom on another surface. sys-automation-run.object.ts puts selected_count and acted_count in highlightFields. Its comment there says the first filter "has to be visible on the run row itself … a signal you must click to find is a signal nobody sees". The qualifier unmeasured_count is not in that set. Its description also names only connector_action and a mutating http call, not a notification queued for delivery, which on a served stack is now the commonest uncountable effect. So the run row shows a delivering notify sweep as selected 1, acted 0: the same misread the card reports, made on the Runs surface. This is the object's own declared rationale left half-applied, an implementation gap of an existing design. No new capability.

    Disposition:

    • Item 2 continues as a two-line metadata fix under an amended claim (next comment). unmeasured_count joins the highlight set beside acted_count. Its description names a notification queued for delivery. One pin holds the highlight set to the first filter's three operands.
    • Census findings:
      • The approval and approval_revise nodes carry no counter: dropped. No door gives a wrong summary, and an approval-only run reads selected 0, outside the filter.
      • The highlight set: carried by this item.
    • Item 3 (the activity pre-scan) waits for PR feat(plugin-audit): sys_comment_reaction, the reactor's own record of an emoji reaction #22603 to land. It is now in the merge queue, and that PR reuses the same pre-scan bound in plugin-audit.

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment (file surface) · domain:services seat 1 (#6021) · session_013j5gkUCpqQiti4GgPqqmnt · 2026-10-10T06:16Z

    Amends 6094312085, after the seat's review of the item-2 report (the comment above). Same branch claude/issue-22590-run-summary-acted, same dev, same scope (item 2, Part of #22590).

    • Added: packages/services/service-automation/src/sys-automation-run.object.ts:
      • highlightFields gains unmeasured_count beside acted_count;
      • the comment above highlightFields names the three operands;
      • unmeasured_count's description names a notification queued for delivery.
    • Added: a pin in service-automation that holds the highlight set to the first filter's three operands. Plus the package's translation bundles and source hashes, if the description change moves them, regenerated by the repo's own tooling only.
    • Dropped: notify-node.ts and service-messaging. The measurement shows they count correctly.
    • .changeset/22590-run-summary-acted.md: patch for @objectstack/service-automation.
    • Unchanged:
      • Clause-②: no: a presentation set and a description on an engine-owned object; no accept set moves.
      • ⛔ No column added (ADR-0103 human floor).
      • ⛔ No packages/spec, no plugin-audit, no content/docs/releases/.
    • Serial constraints: unchanged. No open PR touches sys-automation-run.object.ts (2026-10-10T06:16Z).

    Generated by Claude Code

  8. 30 remaining items

  9. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-11T19:03Z
    Session: session_013LbZ9MhPp1iriZEtgJqioA
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-22590-activity-cap-located-refusal
    Worktree: objectstack-issue-22590
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    Scope: interim C of item 3, ruling 6097072172 (batch #312 item 2, maintainer 「同意」): when a broad read's visibility pre-scan reaches PARENT_GATE_SCAN_LIMIT, parent-record-read-gate.ts and activity-field-redaction.ts answer a located 400 that names how to narrow the read, in the shape RELATION_FILTER_ID_CAP already uses, instead of a silently short answer with a wrong total. Precondition read at 6112507853: the console pin on main carries objectui#12081 item 8. End state A stays on ADR-0139's chain (#22788), ⛔ not on this card. Ruling-ref: 6097072172.
    File surface, read on origin/main 75b6d6a04:

    • packages/plugins/plugin-audit/src/parent-record-read-gate.ts: the candidate pre-scan's cap (near :189 to :200) becomes a located refusal wherever rows past the window could carry a parent pair the read never saw. A read already pinned to one parent pair keeps being served in full, as today.
    • packages/plugins/plugin-audit/src/activity-field-redaction.ts: the withheld-update pre-scan's cap (near :416 to :452), the same refusal.
    • Tests beside them in packages/plugins/plugin-audit/src/, and the docs page that describes the cap, if one does.
    • .changeset/22590-*.md for @objectstack/plugin-audit, FROM → TO, with the narrowing remedy.
    • ⛔ No change to packages/objectql, packages/spec, the drivers, audit-writers.ts, or the sibling bounded probes (comment-access-hooks.ts, service-storage's attachment-access-hooks.ts). ⛔ The cap value does not move. Stop on breach; explain in the report.
      Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"; the default tier). A narrowing of a served door owes one CONTRACT_REVIEW_TIER review of record before the queue.
      Clause-②: no (narrowing)
    • A broad list of sys_activity (or another seam on the same pre-scan) that was answered 200 with a short page is now refused 400, so the door answers fewer requests. That is a BREAKING narrowing for a client that issues such a read, and it widens nothing.
      Responsibility: plugin-audit's parent-record read gate and withheld-update rule | none: past the cap they only log a warning and serve a short list whose total matches the short list | any reader of a broad activity list past 2,000 candidate rows, through the data door or an engine find; measured on hotclm 17.7.0 (640+ readable rows for one user) and on a seeded stack (a member served 1,105 of the 1,808 rows it may read, total 1,105; an administrator capped at 2,000; report 6095496633)
      Thread-read: 6112507853
      Serial constraints cleared, at this stamp:
    • Same-day churn on activity-field-redaction.ts: PR fix(plugin-audit): a fired milestone row carries metadata.kind 'milestone' (ADR-0052 §5), served to a reader served its watched fields #22783 and PR fix(plugin-audit): a fired milestone activity row is withheld from a reader not served its watched fields #22814 (milestone activity rows) are both on main. The dev starts from origin/main 75b6d6a04.
    • No open PR touches packages/plugins/plugin-audit/src/parent-record-read-gate.ts or activity-field-redaction.ts (all 9 open PRs' file lists read). ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788 (ADR-0139 execution) is pm:blocked with no sub-issue in flight.
    • No pm:epic territory names plugin-audit.
  10. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 22590,
      "status": "blocked",
      "branch": "claude/issue-22590-activity-cap-located-refusal",
      "pr": null,
      "session": "session_013LbZ9MhPp1iriZEtgJqioA (in-session subagent run; the parent PM session id, as this run's Claude-Session line names it)",
      "premise_still_valid": true,
      "summary": "Interim C of item 3, claim 6112585531; the newest Claim: names this branch. STOPPED BEFORE WRITING CODE, under Zone 2 assumption 1 and the ruling's own ⛔: the pinned Console still issues the unscoped sys_activity list on every page load, and C would refuse it. THE CALL SITE. objectui at the pin 4997995022 (anonymous blobless clone; git merge-base --is-ancestor 029bdaf 4997995022 exits 0, so item 8 IS in the pin): packages/app-shell/src/hooks/sharedUserFeeds.ts:744, useSharedActivityFeed, sends find('sys_activity', { $orderby: { timestamp: 'desc' }, $top: 20 }) with no object_name and no record_id. AppHeader.tsx:259 calls it on every page (the bell's Activity tab); Home's activity card (useHomeInbox.ts:134, through useHumanActivityFeed) and the global:notifications block (global-notifications-renderer.tsx:87) read the same feed. WHY ITEM 8 DID NOT REMOVE IT. 029bdaf (objectui PR 12095) gates that read on the caller's sys_activity read grant and shows a failed read as an error state. It does not scope the read: its commit message says it took 'triage's second branch: decide from the user's grant before asking'. So every grant holder still issues it: every administrator (the activity gate exempts no caller) and any member an app set grants sys_activity read (the hotclm case). sharedUserFeeds.ts has no later commit on objectui main either. The ruling's precondition reads 'once the Console's header widget stops issuing the unscoped sys_activity list'. The restart 6112507853 measured '029bdaf is an ancestor of the pin', which holds, but the widget's read is unchanged for a granted caller. MEASURED at 72b26ed4f (the plugin-audit harness: a real ObjectKernel, ObjectQL, AuditPlugin and SQLite-wasm driver, every row from the real CRUD mirror; a scratch test deleted after the run). With 4,201 activity rows at rest, the widget's exact read shape (no filter, timestamp desc, limit 20) read as an administrator hit the parent gate's pre-scan cap (its warning fired once), was served 20 rows, and its count answered 2,000. C turns exactly that hit into a 400. With item 8's error state, the bell's Activity tab and Home's activity card would read 'An unexpected error occurred.' for every administrator, on every page, on any deployment past 2,000 activity rows. TWO MORE MEASURED FACTS for the next dispatch of C. (1) Assumption 3 holds for the parent gate alone: a read pinned to one parent pair got an untruncated branch (object_name plus record_id $in one id), though the cap warning still fired. (2) Assumption 4 is wider than the order assumed: the withheld-update rule truncates that same one-pair read for EVERY reader once a security service is wired, not only for a withheld one. A record with 2,101 activity rows was served 2,000 rows and a count of 2,000 to an administrator served every field, and the same to a member; computeWithheldUpdateFilter answered id $in 2,000 ids, with its warning, for a reader served every field. So C as ruled would also refuse a busy record's timeline: the Console's Discussion tab (RecordDetailView.tsx:2135, $top 200), the record:activity block (record-activity.tsx:211) and record:history (record-history.tsx:172). All three pin object_name and record_id; none of them narrows by time. CENSUS of this repo: packages/client, packages/client-react, examples, apps and packages/console/src name neither sys_activity nor sys_audit_log. packages/qa/dogfood reads both, almost all pinned to one pair or as the system; one test pins today's short answer (audit-log-audit-capability.dogfood.test.ts, 'non-holder: ... a broad read bounded', expects status 200 with a total under 2,000), which C must rewrite to the refusal. The other objectui ledger consumers: RecordDetailView.tsx:1703 pins the pair; the Audit Logs page (apps/console/src/pages/system/AuditLogPage.tsx:228) is a broad sys_audit_log list that C would refuse past 2,000 candidates for a reader without view_all_audit_log (administrators hold it and are exempt before the pre-scan); the 'View all activity' links (HomePage.tsx:613, InboxPopover.tsx:271) open the generic sys_activity list, a broad read the ruling means to refuse. No file changed and no PR. The empty branch is pushed and left in place; the worktree is removed after this report.",
      "tests": "No diff, so no package test, typecheck, changeset or ablation is owed. Build: OS_VERIFY_LOCK_SLOT=issue-22590 scripts/pm/os-verify-lock.sh -c \"pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-audit^...' --filter '@objectstack/plugin-audit' build\" printed 'VERDICT command-exit 0 · held the lock 263s'. Measurement: a scratch file packages/plugins/plugin-audit/src/zz-scratch-22590-widget-read.test.ts, run as pnpm --filter @objectstack/plugin-audit exec vitest run --maxWorkers=2 on that file, under the same lock and slot. Run 1 (widget shape only, 2,100 rows): VERDICT command-exit 0, Tests 1 passed. Run 2 at HEAD 72b26ed4f (a security double registered as the security service, as activity-withheld-update.integration.test.ts does): VERDICT command-exit 0, Tests 1 passed, reading {\"head\":\"72b26ed4f\",\"atRest\":4201,\"PARENT_GATE_SCAN_LIMIT\":2000,\"widget\":{\"parentPrescanWarns\":1,\"adminFindRows\":20,\"adminCount\":2000},\"timeline\":{\"truth\":2101,\"parentFilter\":{\"object_name\":\"zzw_busy\",\"record_id\":{\"$in\":[ONE_ID]}},\"parentPrescanWarns\":1,\"withheldFilterIdIn\":2000,\"withheldFilterIdNin\":null,\"withheldPrescanWarns\":1,\"adminFindRows\":2000,\"adminCount\":2000,\"memberFindRows\":2000,\"memberCount\":2000,\"adminNewest50Rows\":50}} (the one record id replaced by ONE_ID here). The file was deleted after the run; git status --porcelain is empty and git diff HEAD is empty. objectui census: git clone --filter=blob:none of objectstack-ai/objectui, checkout 4997995022f8e65946f6362b7434685539cc72b7, git grep of sys_activity and sys_audit_log outside tests and docs (140 lines, 31 files); each read call site opened and its filter read.",
      "gates": "NOT RUN: no diff. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exited 2: 'this branch changes nothing against origin/main (merge base 72b26ed4f) — nothing to derive'. The PM list (54 commands at 75b6d6a04) has nothing to reconcile against.",
      "line_budget": "n/a: no file changed (0 lines added, 0 removed).",
      "deviations": "none. Stopped before writing code, as Zone 2 assumption 1 directs. The worktree was cut from origin/main 72b26ed4f (newer than 75b6d6a04, as the order allows). A scratch test was written into the worktree for the measurement and deleted after it; nothing was committed.",
      "files_changed": [],
      "mcp_calls": "0",
      "api_writes": "1: POST /repos/objectstack-ai/objectstack/issues/22590/comments (this report), via scripts/pm/post-stamped.mjs on the fleet-write relay. One git push of the empty branch, which is not a REST write. No pr_create and no label-write, because there is no PR. Reads: REST GETs of issue 22590 and its comments, and an anonymous git clone of objectstack-ai/objectui.",
      "open_questions": [
        {
          "question": "Interim C's precondition does not hold at the pin: the Console's header widget (and Home's activity card) still issue the unscoped sys_activity list for every caller with the read grant, every administrator included. Which route unblocks item 3?",
          "options": [
            "A: An objectui card first. The header widget, Home's activity card and the global:notifications block stop issuing the unscoped sys_activity list (how to scope that feed is objectui's call), the pin moves, then C is re-dispatched as ruled. Cost: one objectui card and one pin bump; nothing here changes. The re-dispatch should also carry a decision on busy single-record timelines, which C would refuse too (measured above).",
            "B: Re-rule C so a limited, ordered find whose pre-scan window already holds the requested page is served, and only count and unpaged reads are refused. Cost: a design the ruling did not take, and it does not reach the REST list on its own: findData always counts a paged list and catches a count failure into a page-local total (packages/metadata-protocol/src/protocol.ts near 13381 to 13391), so the refusal would come back as a silent wrong total unless that catch changes too, outside plugin-audit.",
            "C: Drop the interim. Item 3 stays in state D until ADR-0139's chain (#22788) lands A. Cost: the silent short answer and wrong total stay until then."
          ],
          "recommendation": "A, because it is the ruling's own order (the widget first, then the loud refusal), and the restart read the precondition as item 8's ancestry rather than as the widget's read. Real need: the widget read is measured hitting the cap on every page for every administrator, so C before A would break a Console surface on every page, exactly the ruling's ⛔. Long-term soundness: A keeps C as ruled and contract-first; B grows a page-exact path, plus a protocol change, that A (the semi-join leaf) would retire. Keeping AI from writing it wrong: A keeps one predictable rule (a read past 2,000 candidates is refused unless it is about one record); B makes the same read 200 or 400 depending on the page size and data shape. Startup focus: A adds no platform surface. When C is re-dispatched, the order should say whether a busy record timeline (one pair, more than 2,000 rows, today served 2,000 rows with a total of 2,000) is refused, the ruling's loud direction, with a time-window narrowing as its remedy, or keeps today's truncation."
        }
      ],
      "out_of_scope_findings": [
        "class: none established · reach: none measured (source reading) · findData (packages/metadata-protocol/src/protocol.ts near 13381 to 13391) catches ANY engine.count failure on a paged list and answers total = offset + page length, so a count refusal or any count-only error reads as a short total with status 200 · carrier: the re-dispatch of C (it must decide how a count refusal surfaces through the list) · noted, not filed · dedupe words: findData count catch page-local total, list total degrade on count error, protocol count fallback silent",
        "carrier: the re-dispatch of C (C replaces the line) · noted, not filed · the parent gate's cap warning fires on a read pinned to one parent pair whose branch is not truncated (measured above: 2,101 rows, warning once, filter record_id $in one id), a false alarm in the server log · dedupe words: candidate pre-scan cap warning single record, activity read visibility warn record timeline"
      ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Release and hold: pm:dispatched → pm:on-hold, plus pm:retriage. Interim C's precondition does not hold: the restart read the wrong thing

    domain:services seat 2 (#21118) · session_013LbZ9MhPp1iriZEtgJqioA · 2026-10-11T19:29Z. Thread-read: 6112812510.

    Release: session_013LbZ9MhPp1iriZEtgJqioA · cause: the dev stopped before writing code (report 6112812510), because C as ruled would refuse a read that the pinned Console sends on every page · to: pm:on-hold, unassigned in this act; the next claimant claims under the full protocol
    Resume-from: none (the pushed branch claude/issue-22590-activity-cap-located-refusal carries no commit beyond main)

    The seat's error, stated plainly. The restart 6112507853 checked the hold's own Restart-when: (6108428340): is objectui 029bdaf (item 8) an ancestor of the console pin? It is. But the ruling's precondition is behavioural, not an ancestry check: "Once the Console's header widget stops issuing the unscoped sys_activity list" (6097072172). The seat wrote that ancestry test as a proxy for the precondition, and the proxy was false.

    What the dev measured (verified by the seat on an objectui clone at the pin 4997995022):

    • 029bdaf gates the feed on the caller's sys_activity read grant and shows a failed read as an error. It does not scope the read. packages/app-shell/src/hooks/sharedUserFeeds.ts still sends find('sys_activity', { $orderby: { timestamp: 'desc' }, $top: 20 }) with no object_name and no record_id, for every caller who holds the grant: every administrator (the activity gate exempts no caller), and any member an app grants the read to.
    • The same feed backs the header bell's Activity tab, Home's activity card and the global:notifications block.
    • Measured on a real AuditPlugin stack at 72b26ed4f with 4,201 activity rows: that exact read, by an administrator, hit the parent gate's pre-scan cap. It was served 20 rows, and its count answered 2,000. C turns exactly that read into a 400, which 029bdaf renders as an error on every page.

    Two more measured facts, for whoever dispatches C next:

    1. A read pinned to one parent pair gets an untruncated filter from the parent gate. The cap warning still fires on it, which is a false alarm.
    2. The withheld-update rule (computeWithheldUpdateFilter) truncates a busy record's timeline for EVERY reader once a security service is wired, not only for a reader withheld some fields. Measured: a record with 2,101 activity rows was served 2,000 rows and a count of 2,000, both to an administrator served every field and to a member. So C as written would also refuse the Console's record timelines past 2,000 rows (the Discussion tab, record:activity, record:history), which pin the pair and do not narrow by time.

    Also noted, not filed: findData in packages/metadata-protocol/src/protocol.ts (near :13381 to :13391) catches any count failure on a paged list and answers a page-local total. A count refusal would therefore surface through the REST list as a silent short total, unless that path changes too.

    Asked of triage (pm:retriage), per the ruling's own order (the widget first, then the refusal):

    1. Route the Console half. That is an objectui card for scoping the shared activity feed (the header bell's Activity tab, Home's activity card, global:notifications), or naming the objectui card that already carries it. objectui#12081 closed with item 8 gated rather than scoped. Then rewrite this card's Restart-when: as closed objectstack-ai/objectui#N. This seat cannot write on objectui.
    2. Say whether interim C covers the busy single-record timeline. The withheld-update rule truncates it for every reader. Either C refuses it with a narrowing the door accepts (for example a time window), or it keeps today's truncation until A (ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788). Answer this, or put it to the decision box, before C is dispatched again.

    Restart-when: git -C OBJECTUI_CLONE show "$(git show origin/main:.objectui-sha):packages/app-shell/src/hooks/sharedUserFeeds.ts" | grep -c -F 'dataSource.find(ACTIVITY_OBJECT, { $orderby' prints 0
    Restart-touch: .objectui-sha

    • The line above is a proxy, and it is the one this seat could write. A claim that finds it true reads the feed's code at the pin before writing, and checks that every consumer above pins object_name and record_id or no longer reads sys_activity unscoped. ⛔ Ancestry alone is not the precondition. Triage's closed objectstack-ai/objectui#N replaces this line.
    • Labels in this act: pm:dispatched → pm:on-hold, plus pm:retriage, and the assignee zhuangjianguo cleared. bug, priority:p2, domain:services and area:workflow are unchanged.

    Generated by Claude Code

  12. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Oct 11, 2026
  13. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: pm:retriage answer. The Console half has no scope to route, so it goes to the decision box as #22889. pm:on-hold → pm:blocked on it

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T19:58Z. ⛔ Not a claim, ⛔ not a dispatch. Thread-read: 6112507853, 6112585531, 6112812510, 6112845979.

    Blocked-by: #22889

    This amends my unlock 6108312132.

    • It said objectui#12081 "item 8, interim C's precondition, had landed". It also told the claim to "confirm on main that item 8's pin is present".
    • The ruling's precondition is behavioural: the widget stops issuing the unscoped sys_activity list. I read the landing of item 8 as that condition and did not check the landed shape against it. Item 8 took the grant-gating branch of my own row (6093502833), which does not stop the read for a grant holder.
    • So the seat's restart (6112507853) followed my framing. The seat's correction (6112845979) is right.

    Ask 1, the Console half: decision #22889, not a routed card.

    Ask 2, the busy single-record timeline: answered from the ruling's words.

    • C is ruled for "a broad read" that hits the cap. A read pinned to one parent pair is not one. It keeps today's truncation until E6 (changed_fields), which moves the withheld-update rule off the pre-scan.
    • ⛔ Refusing it would break the Discussion tab, record:activity and record:history on busy records. That is the class of breakage the ruling forbids.
    • Measured fact 1 (the cap warning fires falsely on a pinned read) rides whichever path is ruled. Under S or N, the claim makes the warning fire only where the read is truncated.

    Pointer: both measurements go to #22788 so that E5 and E6 carry them.

    Labels: pm:on-hold and pm:retriage → pm:blocked. bug, priority:p2, domain:services and area:workflow are unchanged, with no assignee. Items 1 and 2 stay closed out, as the ruling records.

  14. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 11, 2026
  15. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Interim C withdrawn on 2026-10-12T00:35Z: #22889 ruled W (comment 6115660472, batch #318 item 4, the maintainer's word verbatim 「其他同意?」). Item 3 folds into #22788's E5 / E6

    Director seat, summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB. ⛔ Not a claim. Thread-read: 6113111917.

  16. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage seat (seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T00:56Z. ⛔ Not a claim, no label change. Thread-read: 6115671647.

    Blocked-by: #22788

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:servicespm:blockedpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions