Repository navigation
feat(service-settings,plugin-audit,plugin-sharing): config_change rows name their settings row; record pages for audit rows and queues - #30728
Conversation
…gs row it changed upsertRow reports the id of the stored row a write landed on, and the config_change sink writes it as record_id beside the object the scope selects, so the ledger row and the CRUD mirror's row about the same write share one pointer pair. Claude-Session: https://claude.ai/code/session_016WV4cyf254wVgypzQ5Uj1a Co-authored-by: Claude <[email protected]>
…bout the same record The sys_audit_log record page draws the row's fields in their groups and, beneath them, every ledger row whose record_id is this row's, so a config_change row's digest sits beside the CRUD mirror's row that carries the value. The page label is translated in the eleven shipped locales, and the read-gate prose names config_change among the rows about a record. Claude-Session: https://claude.ai/code/session_016WV4cyf254wVgypzQ5Uj1a Co-authored-by: Claude <[email protected]>
…e name The organization's queue gets a default full record page whose members list reads sys_queue_member keyed on `queue` = this queue's `name` (relationshipValueField), so the console's filter and its "New" prefill both carry the name the member rows store. sys_queue_member.queue is unchanged. Declared queues have no sys_queue row and so no page. Co-authored-by: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_016WV4cyf254wVgypzQ5Uj1a
…n-level ledger row to an object-less action A settings write's config_change row now names the settings row it changed, so the parent-read-gate fixture loses its only row about no record. It now gets one from the action door (an action invoked with no subject record), and pins that the config_change row follows the gate on the settings row it names while the admin is still served it. The settings audit fixture pins record_id against the stored sys_setting row and finds the mirror's row about the same write beside it. Adds the changeset for the three packages. Co-authored-by: Claude <[email protected]> Claude-Session: https://claude.ai/code/session_016WV4cyf254wVgypzQ5Uj1a
|
domain:server#5 · os-elon-musk · session_016WV4cyvF7SP65frVFAYLv7 席位核对 · ACCEPTPR #30728 · head 维护者速读(席位校正)The body's lines stand as written, read against the diff. One consequence is the ruling's own and is named in the 风险 line and the changeset: a reader holding only the ledger's read grant, who cannot read the settings store, no longer sees NEW 席位核对
|
📓 Docs Drift CheckThis PR changes 3 package(s): 58 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 15 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a1f4c72d067f7886d24ae81d1b912f94eb87d4ba && git checkout a1f4c72d067f7886d24ae81d1b912f94eb87d4ba
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ab44ec94b0362bafdf5f610b1d121655cd39a5fa f6c2de448e069709b45b53d3b75055dd2df805a3 && git checkout -B drift-repro ab44ec94b0362bafdf5f610b1d121655cd39a5fa && git merge --no-ff f6c2de448e069709b45b53d3b75055dd2df805a3
node scripts/docs-audit/affected-docs.mjs --json ab44ec94b0362bafdf5f610b1d121655cd39a5fa
|
Requested by Elon · project thread
Refs #30209
Clause-②: no
Metadata-protocol: none
维护者速读
config_change审计行现在把被改的设置行 id 写进record_id;sys_audit_log新增默认记录页,列出「同一记录的其他变更」;sys_queue新增默认记录页,成员列表按队列name过滤,「新建」预填队列名。record_id,记录页就能把同一设置行的镜像 create/update 行(带非机密明文值)摆在一起;队列成员按名字存,记录页才能直接管成员。config_change行有了record_id,就归父记录读门管,只持有审计台账读授权、读不到设置表的人从此看不到新的config_change行(管理员及持view_all_audit_log者照看;旧行record_id为空,照旧可见)。席位已接受此后果,维护者可在本 PR 否决。其余是新增页面,不改sys_queue_member.queue,不碰 spec 与 ui。回滚即撤本 PR。packages/services/service-settings/src/settings-service.ts的upsertRow与config-change-audit.ts的record_id;packages/plugins/plugin-audit/src/sys-audit-log.page.ts;packages/plugins/plugin-sharing/src/sys-queue.page.ts;packages/qa/dogfood/test/audit-log-parent-read-gate.dogfood.test.ts新增的读门用例。pnpm --filter @objectstack/plugin-sharing exec vitest run src/sys-queue-page.test.ts(8 条全绿)与pnpm --filter @objectstack/service-settings exec vitest run src/config-change-audit.test.ts(18 条全绿)。What changes
Row 4 B: a
config_changerow names the settings row it changed, and an audit row's record page lists the other changes to the same record.@objectstack/service-settings:SettingsService.upsertRowreturns the stored row's id (rowId) from the update branch (the probed row) and the insert branch (the inserted row), and null on the in-memory paths. The audit call passes it asrecordId, a new optional field on theSettingsAuditSink.recordentry, andbuildConfigChangeAuditSinkwrites it torecord_id.object_namealready named the store (sys_setting, orsys_platform_settingfor a global value).metadataand the digest innew_valueare unchanged.@objectstack/plugin-audit: a default full record page forsys_audit_log(sys_audit_log_detail, served through the plugin manifest'spages). It draws every field butidin the object's own field groups, and arecord:related_listoversys_audit_logwithrelationshipFieldandrelationshipValueFieldbothrecord_id, newest first, withactions: []so there is no "New" on the append-only ledger. The page label is translated in all eleven locales (translations/pages.ts, wired throughwithSourceFallbackwith recorded source hashes and declared in the extract config).by_objectgrouping of share and audit records name the target object by its API name (object_name), outside the list columns #24501 covers #24611 comment insys-audit-log.object.tsand the row-class prose inaudit-log-read-visibility.tsnow sayconfig_changerows name their settings row, and that olderconfig_changerows (andimportrows) do not. The gate code is unchanged.Row 6 B: a queue's record page lists its members.
@objectstack/plugin-sharing: a default full record page forsys_queue(sys_queue_detail). Its members list readssys_queue_memberwithrelationshipField: 'queue'andrelationshipValueField: 'name', so the console filters onqueueequal to this queue'snameand its "New" opens the member form withqueuefilled in with that name.sys_queue_member.queueis unchanged. The page label ships in the plugin's four locales.sys_queuerow, so it has no record page. Its organization's member rows stay on the Queue Members list.Tests
service-settings/src/config-change-audit.test.ts(18 pass): the global case pinsrecord_idto thesys_platform_settingrow's id, the tenant case to thesys_settingrow's id (the re-pin near the old L386), and a new case writes, overwrites and clears one key: one stored row, three ledger rows with the samerecord_id, andmetadatakeys exactly as before.plugin-audit/src/sys-audit-log-page.test.ts(10 pass): the manifest carries the page;PageSchemaand each node'sComponentPropsMaprow parse it (with a control the door refuses); the details cover every field butid; the list props; the eleven locales.plugin-audit/src/sys-audit-log-same-record.integration.test.ts(6 pass), on ObjectQL, the SQLite driver and the real CRUD mirror, with the filter read off the page: beside a globalconfig_changerow the list holds bothconfig_changerows and the mirror's create and update; the mirror's update carries the old and new value in plain text (the measure-first reading: yes); a tenant row lists only its own rows; a secret's mirror row carries neither the value nor its handle.plugin-sharing/src/sys-queue-page.test.ts(8 pass), on ObjectQL and SqlDriver over in-memory SQLite: the filter{ queue: queue.name }read off the page lists that queue's members and no other's (control: keyed on the id it finds nothing); the "New" prefill{ queue: 'billing' }saves a member the list shows; and the list agrees withOwnershipQueueService.resolveQueueon the members.settings-config-change-audit.dogfood.test.tsadds an end-to-end pin thatPUT /api/settings/brandingwrites aconfig_changerow whoserecord_idis the storedsys_settingrow's id, with the mirror's row about the same write beside it carrying the value.audit-log-parent-read-gate.dogfood.test.tsmoves its run-level row to an object-less action invocation (the settings write no longer supplies one, and this boot has no other run-level producer), and adds a pin that theconfig_changerow follows the gate on its settings row: the ledger-only reader gets 404, the admin 200.scripts/ablation-replace.mjs, each restored to the HEAD blob withgit diff HEADempty:record_id: entry.recordId ?? nulltorecord_id: nullturns 3config-change-auditcases red; the queue page'srelationshipValueField: 'name'to'id'turns 4sys-queue-pagecases red.@objectstack/service-settings69 files, 1179 tests;@objectstack/plugin-audit75 files, 1161 tests;@objectstack/plugin-sharing67 files, 1417 tests; all pass, andtsc --noEmitexits 0 in each.Acceptance notes
config_changerows unless it can read the settings row they name. Holders ofview_all_audit_logsee every row. The changeset and the 风险 line say this so the maintainer can veto it here.audit:logbrowser drawer, not this record page. The page opens from the object's list and from record links. Linking the drawer to the page is a ui change, outside this PR.record_idsends no query and shows the empty state. The list includes the row being read.config_changeslist view's columns; the fixture inaudit-log-deployment-level-rows.integration.test.ts, whose null-record_idconfig_changerow teststenant_id, not the record pointer; and open PR fix(service-settings): a clear goes through an env pin, so the allowed-IP lockout recovery survives removing the variable #30448, which editssetMany's env-lock branch in the same file but notupsertRowor the audit call.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 69 commands on head f6c2de4 (origin/main merged in). Each ran with its exit code captured before any pipe: 69 exit 0.check-plugin-teardown-shape --self-testexited 3 on the first pass (shallow clone, the pinned fixture commit unreachable); after fetching621a4876it exits 0.--ranreconciliation: "69 derived famil(ies) accounted for — 69 run, 0 NOT-MEASURED (a DERIVED zero — all 69 recorded an exit code and none of them is 3)".pnpm lint: exit 0.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cyf254wVgypzQ5Uj1a