You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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
All three verified on 18 at 117301705 and ticked: report 6250571009, close-out 6250603283.
1. A file field renders "no file" for a user who can read the record but not sys_file. As a requester (permission set grants read on clm_contract_version, not on sys_file), opening the version they just uploaded shows no file; server log: sys_file lookup failed; file fields keep their raw ids and will render as "no file" (screenshot 036). Expected: a file field's hydration follows the reader's access to the record that holds it (as sys_attachment reads follow the parent record), or the refusal is surfaced as a refusal, not as an empty field.
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.
Lane for items 2 and 3:service-automation and plugin-audit, both domain:services. They are unrelated, so they may land as two PRs (Part of #22590).
Why p2: a scheduled flow that did its work reads the same as one that had nothing to do, and a reader can silently miss activity they are allowed to see.
Measure which node kinds count today, and make the count read from each node's outcome, not from a list of record-writing kinds.
Pin: a flow that only notifies reports acted ≥ 1. Control: a run with nothing to do reports 0.
Item 3: the activity read pre-scan cap
plugin-audit/src/activity-read-visibility.ts, with the bound shared with the comment gate.
Direction: the visibility decision moves into the query, as a predicate the driver evaluates, instead of a bounded candidate pre-scan. ⛔ Raising the cap is not the fix: it only moves the cliff.
⛔ It never fails open. Fail-closed stays the behaviour on any error.
If the predicate cannot be pushed down for some rule, the report names that rule.
Pin: a reader with more than the cap's worth of readable candidates still gets every visible row. Control: an unreadable row stays hidden.
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.
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:
With an app allowRead on sys_file (no readScope), three requesters — one owning neither contract — each listed everysys_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).
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 main25be876.
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 #98Blocked-by this card).
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/mainb27b89474d:
packages/services/service-automation/src/builtin/notify-node.ts: the emit path's metrics (about :490–:515), which reports acted: delivered, or unmeasuredEffect when anything is enqueued.
packages/services/service-messaging/src/**, only if the measurement shows that a delivery it completes synchronously is reported as enqueued. Then the fix is the producer's count, not the node's.
Tests in the package(s) touched, covering triage's pins.
.changeset/22590-*.md:patch for each package that publishes a change.
Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; the default tier)
Clause-②: no
A counter that under-reports is corrected at its producer. No accept set and no public surface move.
Responsibility: this repository's service-automation (notify node) or service-messaging: a notify-only run's summary reads as a run with nothing to do | #4354's own counters (acted / unmeasuredEffect), which are the right home and are reused | every operator of a scheduled flow; measured on 17.7.0 in objectstack-ai/hotclm (five of the app's six daily flows)
Thread-read: 6093572841
Serial constraints cleared:
No open PR touches notify-node.ts, service-messaging or plugin-audit's activity-read-visibility.ts (10 open PRs, file lists read at this stamp, 2026-10-10T05:42Z).
{
"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"
]
}
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 maine22315238f, 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.
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).
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).
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.
Ruling 6097072172's interim C ("a located 400 when a broad read hits the pre-scan cap, once the Console's header widget stops issuing the unscoped sys_activity list") is withdrawn: its precondition is not what objectui#12081 item 8 landed (029bdaf4 reads only for a caller with the grant and does not scope the read), and the platform has no model that would give the site-wide feed a natural scope. The end state A (ADR-0139, ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788) stands; the state until E5 is what the ruling called D: nothing in the platform moves for item 3.
Landing carry from domain:spec seat 1 (#6017) · os-warren · session session_019G5ZnRrTMiwcHG6iZM19zf · 2026-10-13T01:23Z. ⛔ Not a claim, and no label change.
PR #22993 (#22870 PR 2) landed as 1e62f71bf6. It adds two read gates that consume resolveReadableParentIds, now hoisted to @objectstack/core:
the sys_mail_association gate;
the sys_mail_message gate, in packages/services/service-mailbox/src/mail-read-visibility.ts.
ADR-0139 (Proposed) lists six probe consumers, as read on d8830c28. These two are a seventh and an eighth. The record forbids no new consumer, but when ADR-0139's execution cards are cut, its E7 switch card should name both mail gates in its consumer list, so the switch covers them. The ruling ADR-0139 answers came from this card, so the pointer is left here.
Items 1 and 2 (a file field read as "no file" without sys_file read; summary.acted 0 while notifying) were measured on 17.7.0. Re-measure each on 18 before any dev starts. An item that no longer reproduces is ticked with its reading, not carried.
Claim: PM loop round 6 · 2026-10-18T13:06Z · a measurement round: verify items 1–3 on 18 and tick or carry each, with no code change expected
Session: session_01Nk17Z26X5DB59iQztDBADH (the dispatching seat; the dev runs mode:subagent in this session's container and shares its session ID, so its identity is the branch)
Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-22590-verify-on-18
Worktree: objectstack-issue-22590
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface (at origin/main117301705 or later; stop on a breach and explain it in the report):
NONE for product code. This round measures, on a booted stack and by source reading. A temporary measurement test may live in the worktree for the readings; it is never committed.
{
"issue": 22590,
"status": "done",
"branch": "claude/issue-22590-verify-on-18",
"pr": null,
"session": "session_01Nk17Z26X5DB59iQztDBADH (mode:subagent run in the dispatching seat's container; the parent's id, so the branch is this run's identity)",
"premise_still_valid": false,
"summary": "Measurement round, claim 6250273682 (the newest Claim: names this branch). No product file was edited and nothing was committed. Every reading is from ONE real boot at origin/main 117301705 (18.0.0-next.3): bootStack with the real SecurityPlugin, ObjectQL, SQL driver, REST and auth, the served slate's messaging, plus AuditPlugin, StorageServicePlugin and the automation service, read through the HTTP door. main has since moved to ec749fbb4 (#24320, a search refusal in engine.ts); zero of its changed lines name resolveFileReferences, sys_file or ReadableIds, so the readings hold there by source diff (not re-measured). NONE OF THE THREE ITEMS REPRODUCES ON 18; each can be ticked with its reading. ITEM 1 (premise false on 18). What landed: #22593's loud half is PR #22620 -> 02d9f69e5a (a refused sys_file hydration reads { id, metadataRefused: true }, not a bare id); the access half went to decision #22624 (ruled B) and was executed by #22637, PR #22697 -> 1bb1da1bcb (a field-owned file hydrates by the download door's field-owned verdict); follow-up #22698 (spec prose) is closed completed; the Console renders the marker as a restricted state (objectui 065a1cfbf, objectui#12339) and that commit is in the pinned .objectui-sha 0707b4ae37 (is-ancestor exit 0). Every one of those merges is an ancestor of 117301705 (exit 0). Measured: a member whose set grants read and create on m90_doc and NOT sys_file (its direct GET /data/sys_file/ID answers 403 PERMISSION_DENIED) creates a record naming its own committed file through the door (201; the claim stamps ref_object m90_doc, ref_id the record, ref_field attachment). GET /data/m90_doc/ID answers 200 with attachment { id, name, size 2048, mimeType application/pdf, url /api/v1/storage/files/ID }, and the list read the same. So the reader sees the file, not 'no file'. Controls: a record with no file reads attachment null; a reader granted sys_file read gets its own file hydrated. Log: the 'sys_file lookup failed ... will render as no file' WARN is absent from every read. The engine took the refusal arm instead: its debug line (not printed at the default info level) 'sys_file read refused for this caller; file fields carry the refused marker' with refusedIds 1, parentDerivedIds 1. One WARN line remains per read, from the security layer, not the hydration: '[Security] Access denied: operation find on object sys_file is not permitted for positions [org_member, everyone]' (see out_of_scope_findings). Open halves of #22593: none; both halves are landed and #22698 is closed. ITEM 2 (premise false on 18; reads exactly as 6095184121 describes). POST /api/v1/automation/m90_notify_only/trigger (start, notify to admin@objectos.ai on inbox, end) as the admin: 200, summary selected 1, acted 0, unmeasured 1, failed 0; node notify selected 1, unmeasured 1. The sys_automation_run row: completed, selected_count 1, acted_count 0, unmeasured_count 1. The inbox row was absent at the response (29 ms) and present at 55 ms, so the delivery is queued (reliable delivery on, the served default). Control m90_notify_nobody (audience role:no_such_role_22590): summary 1/0/0, run row 1/0/0, no inbox row in 5 s, inside the broken-sweep first filter as designed. highlightFields on the registry and on GET /api/v1/meta/object/sys_automation_run (200) are flow_name, node_id, status, selected_count, acted_count, unmeasured_count, correlation, started_at, updated_at, so PR #22632 (1b99388505) is live. A notify-only run that did its work is distinguishable from one with nothing to do, at the door and on the run row. ITEM 3 (premise false on 18; the cap log is gone on a non-admin page load). The Console's page-load feed at the pin is no longer unscoped: packages/app-shell/src/hooks/sharedUserFeeds.ts reads sys_activity with $filter object_name $in ROSTER (the user's app objects), $orderby timestamp desc, $top 20, and only for a caller holding sys_activity read. Record pages read the pinned pair: RecordDetailView.tsx (timestamp asc, $top 200), record-activity.tsx and record-history.tsx (timestamp desc). Fixture: 4,258 activity rows at rest, all stamped (0 unstamped). A member owns a busy record (2,102 rows) and a calm one; a writer owns a third record (2,102 rows) the member cannot open; the member may read 2,133 rows. As the member: the roster feed answers 200, 20 rows (the 20 newest the member may read, in order), total 2,133; walking it serves exactly the 2,133 expected ids (missing 0, extra 0), total 2,133 on every page; the busy record's Discussion-tab read answers 200, 200 rows, total 2,102, and walking the pinned pair serves all 2,102 (missing 0, extra 0); the writer's record answers 0 rows. As the administrator: the roster feed total is 4,235 (every row about the object) and the pinned walk serves all 2,102. ZERO cap-warning lines across all of those reads, including the pinned ones (the false alarm on pinned reads noted at 6113122770 is gone too). Log-capture control: after 2,000 rows about a D5.2 parent (a fixture object given an object-scoped read middleware), the member's UNSCOPED read logs '[audit] activity read visibility: the bounded probe's candidate pre-scan (object-middleware: ...) hit the 2000-row cap ...' twice, so the capture is armed. The roster feed and the pinned timeline stay at 0 lines and unchanged totals. #22788's two cases: (a) a site-wide read answers its true total, not 2,000: holds; (b) a busy record's pinned timeline answers every readable row, not 2,000: holds. Housekeeping: the remote branch was pushed empty (no commit beyond 117301705) and is left in place. The worktree is removed after this report.",
"tests": "All under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-22590-verify18 and NODE_OPTIONS=--max-old-space-size=4096, at HEAD 117301705. (1) Build: pnpm exec turbo run build --filter='@objectstack/verify...' --concurrency=2 printed 'Tasks: 49 successful, 49 total' and 'VERDICT command-exit 0 · held the lock 377s'. (2) The landed pins, one lock call, each vitest exit captured before any pipe: 'VERDICT command-exit 0 · held the lock 116s'. Item 1: objectql src/engine-file-hydrate-refused.test.ts + src/engine-file-hydrate-parent-derived.test.ts: Test Files 2 passed, Tests 24 passed. plugin-security src/file-field-hydration-refused.test.ts (real SecurityPlugin + SqlDriver): Tests 4 passed. service-storage src/field-owned-file-hydration-door-agreement.test.ts: Tests 9 passed. Item 2: service-automation src/sys-automation-run-first-filter-highlight.test.ts: Tests 2 passed. Item 3, E5 (PR #23693 -> 69351e985a) and E6 (PR #23814 -> 5b6c257b06): plugin-audit src/parent-record-read-leaf.integration.test.ts + src/activity-withheld-update.integration.test.ts: Test Files 2 passed, Tests 45 passed. dogfood test/activity-withheld-row-predicate.dogfood.test.ts (bootStack, HTTP door, 4,203 planted rows): Tests 4 passed. The pins for #22788's cases, by file and case: (a) the site-wide read: parent-record-read-leaf.integration.test.ts 'an administrator’s site-wide read (newest first, 20) is served 20 rows and a true total, not the window' and 'a member is served every activity row it may read past the old window, and the total agrees' (E5), plus activity-withheld-row-predicate.dogfood.test.ts 'an administrator’s site-wide read (newest first, 20) is served the 20 newest rows and a total past the old window' (E6). (b) the busy record: activity-withheld-row-predicate.dogfood.test.ts 'a member is served every activity row it may read, past the old window, and every page reports that total', with its control 'the member is served no update row whose every recorded key it is withheld, and no row about a record it cannot open' (E6). Its read filters by object_name only, and the member's readable rows are one record's 2,050+ rows, so it holds the case by data rather than by the pinned-pair read shape. (3) Booted measurement: one temporary file packages/qa/dogfood/test/zz-measure-22590.dogfood.test.ts, run as pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/zz-measure-22590.dogfood.test.ts: 'Tests 4 passed (4)', 'VERDICT command-exit 0 · held the lock 48s'. The 4,232 bulk rows planted in 3.3 s, so a booted reading past 2,000 rows is feasible on this box and was taken. The readings in summary come from its JSON output. The file was deleted after the run and never committed: git status --porcelain empty, git diff HEAD empty, 0 commits in origin/main..HEAD. No ablation: no behaviour was changed.",
"gates": "No diff. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exited 2: 'this branch changes nothing against origin/main (merge base 117301705) — nothing to derive', so nothing is reconciled with --ran. No PR, so no CI check-runs.",
"files_changed": [],
"line_budget": "0 added / 0 removed",
"deviations": "(1) Item 1's sys_file rows were inserted as the system, with the member as owner, standing in for the upload; the record write that claims the file went through the door as the member. The bytes door (GET /api/v1/storage/files/ID) was not measured. (2) Item 3's bulk rows were inserted as the system, as the landed E6 pin does, not through the CRUD mirror; all were stamped (unstamped 0). (3) The log-capture control for item 3 uses a fixture object with a pass-through object-scoped read middleware (D5.2 by construction) and 2,000 rows about fake record ids. Synthetic on purpose; it measures the surviving probe, not a product read. (4) NOT MEASURED: the Console in a browser. Reason: this round reads the backend door. The page-load read shapes are read from objectui source at the pin 0707b4ae37, and the restricted-state rendering is placed by ancestry only. (5) NOT MEASURED: item 2 under inline delivery (reliableDelivery false). Reason: the served default was measured, and round 0 (6094553100) recorded the inline answer acted 1. (6) NOT MEASURED: objectstack-ai/hotclm itself. Reason: the repository is not attached to this session. (7) One measurement run, not repeated. (8) A git grep over the blobless objectui clone fetched blobs one by one and was stopped before it answered. Not a reading; the three named files were read instead. (9) Readings are at 117301705. The one newer commit on main, ec749fbb4, was checked by source diff only.",
"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, no label write, no other comment. Reads: REST GETs of issues 22590, 22593, 22637, 22788, 22698, 22624 and objectstack-ai/objectui#12030, their comment lists where read, and comment 6113122770; an anonymous blobless clone of objectstack-ai/objectui into this run's scratch directory.",
"open_questions": [],
"out_of_scope_findings": [
"class: a (a server-log line, reproduced) · reach: public door GET /data/OBJECT/ID and the list GET /data/OBJECT on the booted served composition at 117301705. Each read, by a reader with record read and without sys_file read, logs one WARN '[Security] Access denied: operation find on object sys_file is not permitted for positions [org_member, everyone]', while the response itself is correct (the field-owned file is hydrated). The reader never asked for sys_file. · producer: packages/objectql/src/engine.ts:13184 (resolveFileReferences reads sys_file as the caller, then takes the refusal arm) -> packages/plugins/plugin-security/src/security-plugin.ts:2839 (ctx.logger.warn of the developer message). The engine demoted its own line to debug for this reason (engine.ts comment near :13213, 'a warn here fired once per read for every reader without sys_file read'), and the activity gate fixed the same shape by asking canReadObject before reading (parent-record-read-gate.ts header, 'A skipped object is not an access attempt'). The seat decides whether it is filed. · dedupe words: sys_file hydration access denied warn every record read, security access denied sys_file positions org_member file field, resolveFileReferences caller read logs denial",
"carrier: 承接者:无 · noted, not filed · #22788 case (b) has no landed pin at the pinned-pair read shape (object_name plus record_id) past 2,000 rows. The E6 dogfood member case reads by object_name, with a readable set that is one record. This round's booted reading at the Console's own pinned shapes (2,102 rows: Discussion asc 200, record-activity desc 21, full walk) holds it, member and administrator alike.",
"carrier: ADR-0139 D5 (ruled; ruling W 6115671647 keeps D5's classes on the bounded probe) · noted, not filed · the cap warning survives for D5 parents. On this boot the probe named object-middleware parents sys_attachment, sys_comment_reaction, sys_jwks, sys_platform_setting, sys_setting, sys_setting_audit and sys_user_position. With 2,000 or more rows about them, an UNSCOPED broad read logs the warning twice (rows and total), fail-closed for those parents' rows. The Console's page-load feed at the pin is roster-scoped and does not reach it (measured)."
]
}
Verified on 18 and closed · domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-18T13:40Z
The measurement round under claim 6250273682 (report 6250571009) re-measured all three items on one real boot at origin/main117301705 (18.0.0-next.3). None reproduces. Each item is ticked in the body with its reading, as triage's unlock asks (6248493663), and ruling W's close-out for item 3 (6115671647) is done.
As a member with record read and no sys_file read, the record read answers 200 with the file hydrated (id, name, size, mimeType, url). The member's own GET /data/sys_file/ID answers 403. The "will render as no file" WARN is absent. Controls: a record with no file reads null, and a reader with sys_file read is hydrated.
Item 2, the run summary: reads as 6095184121 describes. A notify-only flow answers selected 1, acted 0, unmeasured 1, and the run row matches. The inbox row lands about 55 ms later, because reliable delivery queues it. The control with nothing to do answers 1/0/0 with no inbox row. sys_automation_run's highlightFields carry unmeasured_count (PR fix(service-automation): the run row's highlight set carries the first filter's qualifier, unmeasured_count #22632, 1b99388505).
Item 3, the 2,000-row pre-scan: the cap log is gone on a non-admin page load. There were 4,258 activity rows. A member who may read 2,133 is served a true total of 2,133 on the console's roster-scoped feed, and the full walk serves exactly those ids. The member's busy record (2,102 rows) serves all 2,102 on the pinned read. A record the member cannot open serves 0. Zero cap-warning lines appeared across those reads. A log-capture control proved the capture armed.
Activity
objectstack-fleet commentedon Oct 10, 2026
Triage: first grade,
bug·priority:p2·domain:services·area:workflow·pm:queue. Items 2 and 3 stay here; item 1 is split to #22593Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T03:04Z. ⛔ Not a claim, ⛔ not a dispatch.mainfirst. Each item was measured on 17.7.0.domain:engine, p2): the file-field hydration inobjectql. The loud half is in scope there. The access half (does a file follow its holding record?) would loosensys_file's read boundary, so it goes to the maintainer through a decision card from the claiming seat.service-automationandplugin-audit, bothdomain:services. They are unrelated, so they may land as two PRs (Part of #22590).Item 2: the run summary's
actedactedcounts a run that produced an effect, a notification included. That is the distinction Surface flow run summaries (selected / acted / skipped) — a scheduled flow that does nothing is currently indistinguishable from one with nothing to do #4354 set out to make.acted≥ 1. Control: a run with nothing to do reports 0.Item 3: the activity read pre-scan cap
plugin-audit/src/activity-read-visibility.ts, with the bound shared with the comment gate.objectstack-fleet commentedon Oct 10, 2026
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_filegrant, not only its rendering.GET /api/v1/data/clm_contract_type/IDreturnstemplate_fileas 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_intakeresume withdraft_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
objectstack-fleet commentedon Oct 10, 2026
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:
allowReadonsys_file(no readScope), three requesters — one owning neither contract — each listed everysys_filerow 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_DENIEDfor a non-reader of the parent).sys_filedeclares nosharingModel, soplugin-sharing's effective model treats it as public with no row filter;service-storageinstalls parent-record read visibility forsys_attachmentonly;objectql'sresolveFileReferencesreadssys_filewith the caller's own context. All three unchanged onmain25be876.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_fileread-visibility filter onref_object/ref_idlikesys_attachment's. hotclm waits on it (Enhance currency field type with multi-currency support and configurable column splitting #98Blocked-bythis card).Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Claim: PM loop round 1 · 2026-10-10T05:42Z
Session:
session_013j5gkUCpqQiti4GgPqqmntAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-22590-run-summary-actedWorktree:
objectstack-issue-22590Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)Scope: item 2 only (the run summary's
acted), asPart of #22590. Item 1 is #22593 (triage6093122404). Item 3, the activity pre-scan, is a separate PR after this one, and the card returns topm:queuebetween them.File surface, read on
origin/mainb27b89474d:main, through the trigger door. Read the summary'sselected,actedandunmeasured, and the inbox rows it wrote. Contract:packages/spec/src/automation/execution.zod.ts(Surface flow run summaries (selected / acted / skipped) — a scheduled flow that does nothing is currently indistinguishable from one with nothing to do #4354;unmeasuredEffectis "an effect I cannot count").packages/services/service-automation/src/builtin/notify-node.ts: the emit path'smetrics(about:490–:515), which reportsacted: delivered, orunmeasuredEffectwhen anything is enqueued.packages/services/service-messaging/src/**, only if the measurement shows that a delivery it completes synchronously is reported as enqueued. Then the fix is the producer's count, not the node's..changeset/22590-*.md:patchfor each package that publishes a change.packages/spec: the counter contract is fixed. If the fix needs it, stop and report. ⛔ Noplugin-audit(item 3). ⛔ Noobjectql(item 1, objectql: a file field reads "no file" for a record reader without sys_file read: the hydration lookup runs as the caller, and a refusal reads as absent data (split from #22590 item 1) #22593). ⛔ Nocontent/docs/releases/.Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; the default tier)Clause-②: no
Responsibility:
this repository's service-automation (notify node) or service-messaging: a notify-only run's summary reads as a run with nothing to do | #4354's own counters (acted / unmeasuredEffect), which are the right home and are reused | every operator of a scheduled flow; measured on 17.7.0 in objectstack-ai/hotclm (five of the app's six daily flows)Thread-read: 6093572841
Serial constraints cleared:
descriptionhave no key in theflowstranslation face — the zh-CN intake wizard translates its labels and placeholders and keeps its options and body text in English (17.7.0) #22507 (domain:spec) declaredservice-automation/src/builtin/screen-nodes.ts(6093845640). [v18] retire the{var}template dialect in flow assignment slots: refuse at registration with per-spelling remedies (the C half of #11182 ruling D, on the v18 train) #19939 (domain:spec) declaredservice-automation/src/builtin/template sites. Both are disjoint files; whichever lands later mergesmain.notify-node.ts,service-messagingorplugin-audit'sactivity-read-visibility.ts(10 open PRs, file lists read at this stamp, 2026-10-10T05:42Z).Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
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
objectstack-fleet commentedon Oct 10, 2026
Seat review of the item-2 report: the producer premise is false; the residual is the run row's highlight set ·
domain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T06:16ZReport
6094553100(premise_still_valid: false, no PR). Claim6094312085. Read againstorigin/main, not the report.The measurement holds. On a stack composed as
servecomposes it (reliable delivery on, the default), a run whose only effect is anotifystep answersselected 1, acted 0, unmeasured 1, both on the trigger door's summary and on thesys_automation_runrow.unmeasuredEffectbecause the in-app message is enqueued. The outbox writes the inbox row about 50 ms after the response.acted 1.selected 1, acted 0, unmeasured 0, which is inside the broken-sweep first filter, as designed.execution.zod.ts's contract (an enqueued effect is uncountable, never a fabricatedacted) is what the code does.actedread alone.For the
repo:hotclmseat: read the triple (selected,acted,unmeasured), neveractedalone. A served stack's notify-only sweep readsacted 0withunmeasured 1.The residual, read on
maine22315238f, is presentation, and it is this item's symptom on another surface.sys-automation-run.object.tsputsselected_countandacted_countinhighlightFields. 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 qualifierunmeasured_countis not in that set. Its description also names onlyconnector_actionand a mutatinghttpcall, 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 asselected 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:
unmeasured_countjoins the highlight set besideacted_count. Its description names a notification queued for delivery. One pin holds the highlight set to the first filter's three operands.approvalandapproval_revisenodes carry no counter: dropped. No door gives a wrong summary, and an approval-only run readsselected 0, outside the filter.plugin-audit.Generated by Claude Code
objectstack-fleet commentedon Oct 10, 2026
Claim amendment (file surface) ·
domain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T06:16ZAmends
6094312085, after the seat's review of the item-2 report (the comment above). Same branchclaude/issue-22590-run-summary-acted, same dev, same scope (item 2,Part of #22590).packages/services/service-automation/src/sys-automation-run.object.ts:highlightFieldsgainsunmeasured_countbesideacted_count;highlightFieldsnames the three operands;unmeasured_count's description names a notification queued for delivery.service-automationthat 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.notify-node.tsandservice-messaging. The measurement shows they count correctly..changeset/22590-run-summary-acted.md:patchfor@objectstack/service-automation.Clause-②: no: a presentation set and a description on an engine-owned object; no accept set moves.packages/spec, noplugin-audit, nocontent/docs/releases/.sys-automation-run.object.ts(2026-10-10T06:16Z).Generated by Claude Code
43 remaining items
objectstack-fleet commentedon Oct 12, 2026
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.sys_activitylist") is withdrawn: its precondition is not what objectui#12081 item 8 landed (029bdaf4reads only for a caller with the grant and does not scope the read), and the platform has no model that would give the site-wide feed a natural scope. The end state A (ADR-0139, ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788) stands; the state until E5 is what the ruling called D: nothing in the platform moves for item 3.total, not 2,000; a busy record's pinned timeline answers every readable row, not 2,000.Ruled:line;Blocked-by: objectstack-ai/objectui#12081→Blocked-by: #22788. Labelpm:blockedunchanged. When ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788 closes (E7 landed) the unlock scan returns this card to the queue, and thedomain:serviceslane verifies the two pins onmainand closes it.objectstack-fleet commentedon Oct 12, 2026
Triage seat (seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T00:56Z. ⛔ Not a claim, no label change. Thread-read:6115671647.Blocked-by: #22788
Blocked-by: #22889in6113111917. [Decision] #22590 过渡方案 C 的前提落空:控制台的全站动态读没有记录可收窄。改控制台(S)、把 C 收窄到"只拒绝会答错的页"(N),还是不做过渡、等终态(W)? #22889 is closed (ruled W,6115660472).BODY≠COMMENT), and now they agree.6113122770).objectstack-fleet commentedon Oct 13, 2026
Landing carry from
domain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-13T01:23Z. ⛔ Not a claim, and no label change.PR #22993 (#22870 PR 2) landed as
1e62f71bf6. It adds two read gates that consumeresolveReadableParentIds, now hoisted to@objectstack/core:sys_mail_associationgate;sys_mail_messagegate, inpackages/services/service-mailbox/src/mail-read-visibility.ts.ADR-0139 (Proposed) lists six probe consumers, as read on
d8830c28. These two are a seventh and an eighth. The record forbids no new consumer, but when ADR-0139's execution cards are cut, its E7 switch card should name both mail gates in its consumer list, so the switch covers them. The ruling ADR-0139 answers came from this card, so the pointer is left here.objectstack-fleet commentedon Oct 18, 2026
Unlock:
pm:blocked→pm:queue· p2 ·domain:services· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T09:57ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
completedby its owner at 09:34Z (6248295826). E5 and E6 have landed.main:6115671647). Measure whether the cap's fail-closed log is gone on a non-admin page load. If it is gone, tick item 3 here with that reading.sys_fileread;summary.acted 0while notifying) were measured on 17.7.0. Re-measure each on 18 before any dev starts. An item that no longer reproduces is ticked with its reading, not carried.area:workflowunchanged.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 6 · 2026-10-18T13:06Z · a measurement round: verify items 1–3 on 18 and tick or carry each, with no code change expected
Session:
session_01Nk17Z26X5DB59iQztDBADH(the dispatching seat; the dev runsmode:subagentin this session's container and shares its session ID, so its identity is the branch)Account:
marchtian(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-22590-verify-on-18Worktree:
objectstack-issue-22590Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (at
origin/main117301705or later; stop on a breach and explain it in the report):Container & model:
S: three readings on one booted stack,mode:subagent,model: opus(TIER_DEFAULT; no file surface to tier)Clause-②: no. No change is made.
Responsibility: the three answers are in the card's grade (
6093122404) and rulings (6097072172,6115671647). This round only re-measures them on 18.Thread-read: 6248493663
Serial constraints cleared: nothing else from this seat is in flight (platform-objects sys_organization: a record page has no door to rename an organization — update_organization is list_item only and bundles slug, which the cloud's change_slug orchestrates #24271 landed as
1173017052). The round writes no product file, so no in-flight claim can collide. Item 1's split objectql: a file field reads "no file" for a record reader without sys_file read: the hydration lookup runs as the caller, and a refusal reads as absent data (split from #22590 item 1) #22593 (domain:engine) and item 3's carrier ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788 are both closedcompleted. Serial dispatch, as the maintainer set (「当前任务处理完,后续改为串行派发」, seat post §1).Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 22590, "status": "done", "branch": "claude/issue-22590-verify-on-18", "pr": null, "session": "session_01Nk17Z26X5DB59iQztDBADH (mode:subagent run in the dispatching seat's container; the parent's id, so the branch is this run's identity)", "premise_still_valid": false, "summary": "Measurement round, claim 6250273682 (the newest Claim: names this branch). No product file was edited and nothing was committed. Every reading is from ONE real boot at origin/main 117301705 (18.0.0-next.3): bootStack with the real SecurityPlugin, ObjectQL, SQL driver, REST and auth, the served slate's messaging, plus AuditPlugin, StorageServicePlugin and the automation service, read through the HTTP door. main has since moved to ec749fbb4 (#24320, a search refusal in engine.ts); zero of its changed lines name resolveFileReferences, sys_file or ReadableIds, so the readings hold there by source diff (not re-measured). NONE OF THE THREE ITEMS REPRODUCES ON 18; each can be ticked with its reading. ITEM 1 (premise false on 18). What landed: #22593's loud half is PR #22620 -> 02d9f69e5a (a refused sys_file hydration reads { id, metadataRefused: true }, not a bare id); the access half went to decision #22624 (ruled B) and was executed by #22637, PR #22697 -> 1bb1da1bcb (a field-owned file hydrates by the download door's field-owned verdict); follow-up #22698 (spec prose) is closed completed; the Console renders the marker as a restricted state (objectui 065a1cfbf, objectui#12339) and that commit is in the pinned .objectui-sha 0707b4ae37 (is-ancestor exit 0). Every one of those merges is an ancestor of 117301705 (exit 0). Measured: a member whose set grants read and create on m90_doc and NOT sys_file (its direct GET /data/sys_file/ID answers 403 PERMISSION_DENIED) creates a record naming its own committed file through the door (201; the claim stamps ref_object m90_doc, ref_id the record, ref_field attachment). GET /data/m90_doc/ID answers 200 with attachment { id, name, size 2048, mimeType application/pdf, url /api/v1/storage/files/ID }, and the list read the same. So the reader sees the file, not 'no file'. Controls: a record with no file reads attachment null; a reader granted sys_file read gets its own file hydrated. Log: the 'sys_file lookup failed ... will render as no file' WARN is absent from every read. The engine took the refusal arm instead: its debug line (not printed at the default info level) 'sys_file read refused for this caller; file fields carry the refused marker' with refusedIds 1, parentDerivedIds 1. One WARN line remains per read, from the security layer, not the hydration: '[Security] Access denied: operation find on object sys_file is not permitted for positions [org_member, everyone]' (see out_of_scope_findings). Open halves of #22593: none; both halves are landed and #22698 is closed. ITEM 2 (premise false on 18; reads exactly as 6095184121 describes). POST /api/v1/automation/m90_notify_only/trigger (start, notify to admin@objectos.ai on inbox, end) as the admin: 200, summary selected 1, acted 0, unmeasured 1, failed 0; node notify selected 1, unmeasured 1. The sys_automation_run row: completed, selected_count 1, acted_count 0, unmeasured_count 1. The inbox row was absent at the response (29 ms) and present at 55 ms, so the delivery is queued (reliable delivery on, the served default). Control m90_notify_nobody (audience role:no_such_role_22590): summary 1/0/0, run row 1/0/0, no inbox row in 5 s, inside the broken-sweep first filter as designed. highlightFields on the registry and on GET /api/v1/meta/object/sys_automation_run (200) are flow_name, node_id, status, selected_count, acted_count, unmeasured_count, correlation, started_at, updated_at, so PR #22632 (1b99388505) is live. A notify-only run that did its work is distinguishable from one with nothing to do, at the door and on the run row. ITEM 3 (premise false on 18; the cap log is gone on a non-admin page load). The Console's page-load feed at the pin is no longer unscoped: packages/app-shell/src/hooks/sharedUserFeeds.ts reads sys_activity with $filter object_name $in ROSTER (the user's app objects), $orderby timestamp desc, $top 20, and only for a caller holding sys_activity read. Record pages read the pinned pair: RecordDetailView.tsx (timestamp asc, $top 200), record-activity.tsx and record-history.tsx (timestamp desc). Fixture: 4,258 activity rows at rest, all stamped (0 unstamped). A member owns a busy record (2,102 rows) and a calm one; a writer owns a third record (2,102 rows) the member cannot open; the member may read 2,133 rows. As the member: the roster feed answers 200, 20 rows (the 20 newest the member may read, in order), total 2,133; walking it serves exactly the 2,133 expected ids (missing 0, extra 0), total 2,133 on every page; the busy record's Discussion-tab read answers 200, 200 rows, total 2,102, and walking the pinned pair serves all 2,102 (missing 0, extra 0); the writer's record answers 0 rows. As the administrator: the roster feed total is 4,235 (every row about the object) and the pinned walk serves all 2,102. ZERO cap-warning lines across all of those reads, including the pinned ones (the false alarm on pinned reads noted at 6113122770 is gone too). Log-capture control: after 2,000 rows about a D5.2 parent (a fixture object given an object-scoped read middleware), the member's UNSCOPED read logs '[audit] activity read visibility: the bounded probe's candidate pre-scan (object-middleware: ...) hit the 2000-row cap ...' twice, so the capture is armed. The roster feed and the pinned timeline stay at 0 lines and unchanged totals. #22788's two cases: (a) a site-wide read answers its true total, not 2,000: holds; (b) a busy record's pinned timeline answers every readable row, not 2,000: holds. Housekeeping: the remote branch was pushed empty (no commit beyond 117301705) and is left in place. The worktree is removed after this report.", "tests": "All under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-22590-verify18 and NODE_OPTIONS=--max-old-space-size=4096, at HEAD 117301705. (1) Build: pnpm exec turbo run build --filter='@objectstack/verify...' --concurrency=2 printed 'Tasks: 49 successful, 49 total' and 'VERDICT command-exit 0 · held the lock 377s'. (2) The landed pins, one lock call, each vitest exit captured before any pipe: 'VERDICT command-exit 0 · held the lock 116s'. Item 1: objectql src/engine-file-hydrate-refused.test.ts + src/engine-file-hydrate-parent-derived.test.ts: Test Files 2 passed, Tests 24 passed. plugin-security src/file-field-hydration-refused.test.ts (real SecurityPlugin + SqlDriver): Tests 4 passed. service-storage src/field-owned-file-hydration-door-agreement.test.ts: Tests 9 passed. Item 2: service-automation src/sys-automation-run-first-filter-highlight.test.ts: Tests 2 passed. Item 3, E5 (PR #23693 -> 69351e985a) and E6 (PR #23814 -> 5b6c257b06): plugin-audit src/parent-record-read-leaf.integration.test.ts + src/activity-withheld-update.integration.test.ts: Test Files 2 passed, Tests 45 passed. dogfood test/activity-withheld-row-predicate.dogfood.test.ts (bootStack, HTTP door, 4,203 planted rows): Tests 4 passed. The pins for #22788's cases, by file and case: (a) the site-wide read: parent-record-read-leaf.integration.test.ts 'an administrator’s site-wide read (newest first, 20) is served 20 rows and a true total, not the window' and 'a member is served every activity row it may read past the old window, and the total agrees' (E5), plus activity-withheld-row-predicate.dogfood.test.ts 'an administrator’s site-wide read (newest first, 20) is served the 20 newest rows and a total past the old window' (E6). (b) the busy record: activity-withheld-row-predicate.dogfood.test.ts 'a member is served every activity row it may read, past the old window, and every page reports that total', with its control 'the member is served no update row whose every recorded key it is withheld, and no row about a record it cannot open' (E6). Its read filters by object_name only, and the member's readable rows are one record's 2,050+ rows, so it holds the case by data rather than by the pinned-pair read shape. (3) Booted measurement: one temporary file packages/qa/dogfood/test/zz-measure-22590.dogfood.test.ts, run as pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/zz-measure-22590.dogfood.test.ts: 'Tests 4 passed (4)', 'VERDICT command-exit 0 · held the lock 48s'. The 4,232 bulk rows planted in 3.3 s, so a booted reading past 2,000 rows is feasible on this box and was taken. The readings in summary come from its JSON output. The file was deleted after the run and never committed: git status --porcelain empty, git diff HEAD empty, 0 commits in origin/main..HEAD. No ablation: no behaviour was changed.", "gates": "No diff. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exited 2: 'this branch changes nothing against origin/main (merge base 117301705) — nothing to derive', so nothing is reconciled with --ran. No PR, so no CI check-runs.", "files_changed": [], "line_budget": "0 added / 0 removed", "deviations": "(1) Item 1's sys_file rows were inserted as the system, with the member as owner, standing in for the upload; the record write that claims the file went through the door as the member. The bytes door (GET /api/v1/storage/files/ID) was not measured. (2) Item 3's bulk rows were inserted as the system, as the landed E6 pin does, not through the CRUD mirror; all were stamped (unstamped 0). (3) The log-capture control for item 3 uses a fixture object with a pass-through object-scoped read middleware (D5.2 by construction) and 2,000 rows about fake record ids. Synthetic on purpose; it measures the surviving probe, not a product read. (4) NOT MEASURED: the Console in a browser. Reason: this round reads the backend door. The page-load read shapes are read from objectui source at the pin 0707b4ae37, and the restricted-state rendering is placed by ancestry only. (5) NOT MEASURED: item 2 under inline delivery (reliableDelivery false). Reason: the served default was measured, and round 0 (6094553100) recorded the inline answer acted 1. (6) NOT MEASURED: objectstack-ai/hotclm itself. Reason: the repository is not attached to this session. (7) One measurement run, not repeated. (8) A git grep over the blobless objectui clone fetched blobs one by one and was stopped before it answered. Not a reading; the three named files were read instead. (9) Readings are at 117301705. The one newer commit on main, ec749fbb4, was checked by source diff only.", "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, no label write, no other comment. Reads: REST GETs of issues 22590, 22593, 22637, 22788, 22698, 22624 and objectstack-ai/objectui#12030, their comment lists where read, and comment 6113122770; an anonymous blobless clone of objectstack-ai/objectui into this run's scratch directory.", "open_questions": [], "out_of_scope_findings": [ "class: a (a server-log line, reproduced) · reach: public door GET /data/OBJECT/ID and the list GET /data/OBJECT on the booted served composition at 117301705. Each read, by a reader with record read and without sys_file read, logs one WARN '[Security] Access denied: operation find on object sys_file is not permitted for positions [org_member, everyone]', while the response itself is correct (the field-owned file is hydrated). The reader never asked for sys_file. · producer: packages/objectql/src/engine.ts:13184 (resolveFileReferences reads sys_file as the caller, then takes the refusal arm) -> packages/plugins/plugin-security/src/security-plugin.ts:2839 (ctx.logger.warn of the developer message). The engine demoted its own line to debug for this reason (engine.ts comment near :13213, 'a warn here fired once per read for every reader without sys_file read'), and the activity gate fixed the same shape by asking canReadObject before reading (parent-record-read-gate.ts header, 'A skipped object is not an access attempt'). The seat decides whether it is filed. · dedupe words: sys_file hydration access denied warn every record read, security access denied sys_file positions org_member file field, resolveFileReferences caller read logs denial", "carrier: 承接者:无 · noted, not filed · #22788 case (b) has no landed pin at the pinned-pair read shape (object_name plus record_id) past 2,000 rows. The E6 dogfood member case reads by object_name, with a readable set that is one record. This round's booted reading at the Console's own pinned shapes (2,102 rows: Discussion asc 200, record-activity desc 21, full walk) holds it, member and administrator alike.", "carrier: ADR-0139 D5 (ruled; ruling W 6115671647 keeps D5's classes on the bounded probe) · noted, not filed · the cap warning survives for D5 parents. On this boot the probe named object-middleware parents sys_attachment, sys_comment_reaction, sys_jwks, sys_platform_setting, sys_setting, sys_setting_audit and sys_user_position. With 2,000 or more rows about them, an UNSCOPED broad read logs the warning twice (rows and total), fail-closed for those parents' rows. The Console's page-load feed at the pin is roster-scoped and does not reach it (measured)." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Verified on 18 and closed ·
domain:servicesseat 1 (#6021) ·marchtian·session_01Nk17Z26X5DB59iQztDBADH· 2026-10-18T13:40ZThe measurement round under claim
6250273682(report6250571009) re-measured all three items on one real boot atorigin/main117301705(18.0.0-next.3). None reproduces. Each item is ticked in the body with its reading, as triage's unlock asks (6248493663), and ruling W's close-out for item 3 (6115671647) is done.Item 1, a file field for a reader without
sys_fileread: no longer reproduces. objectql: a file field reads "no file" for a record reader without sys_file read: the hydration lookup runs as the caller, and a refusal reads as absent data (split from #22590 item 1) #22593's two halves are onmain:02d9f69e5a) gives a refused hydration a marker instead of a bare id;sys_fileread (ruling B on #22624) #22637, PR feat(objectql,service-storage): a refused sys_file reader sees a field-owned file's metadata by the download door's verdict (#22637) #22697 (1bb1da1bcb): a field-owned file hydrates by the download door's verdict.As a member with record read and no
sys_fileread, the record read answers 200 with the file hydrated (id,name,size,mimeType,url). The member's ownGET /data/sys_file/IDanswers 403. The "will render as no file" WARN is absent. Controls: a record with no file readsnull, and a reader withsys_fileread is hydrated.Item 2, the run summary: reads as
6095184121describes. A notify-only flow answersselected 1, acted 0, unmeasured 1, and the run row matches. The inbox row lands about 55 ms later, because reliable delivery queues it. The control with nothing to do answers1/0/0with no inbox row.sys_automation_run'shighlightFieldscarryunmeasured_count(PR fix(service-automation): the run row's highlight set carries the first filter's qualifier, unmeasured_count #22632,1b99388505).Item 3, the 2,000-row pre-scan: the cap log is gone on a non-admin page load. There were 4,258 activity rows. A member who may read 2,133 is served a true total of 2,133 on the console's roster-scoped feed, and the full walk serves exactly those ids. The member's busy record (2,102 rows) serves all 2,102 on the pinned read. A record the member cannot open serves 0. Zero cap-warning lines appeared across those reads. A log-capture control proved the capture armed.
69351e985a, E6 PR feat(plugin-audit): sys_activity gains writer-stamped read-rule columns with an idempotent backfill, and the withheld-row rule is a predicate with no SYSTEM pre-scan (ADR-0139 E6) #238145b6c257b06) pass on117301705.Out of scope, with carriers:
sys_filehydration read, though the response is right.The card closes
completedin this act.pm:dispatchedis cleared and the assignee released in the same act.Generated by Claude Code