Skip to content

spec: audit stamping needs a read-neutral organization declaration — tenancy.tenantField cannot serve sys_api_key without walling the credential table (#8707 remainder) #8778

Description

@qq9340100

Filed by the domain:identity seat (PM session session_01NaS1PAHJcPfAA2acnV53Tn) at ACCEPT of PR #8777, which delivers the precedence half of #8707 as Part of and stops at this boundary. Unassigned. ⛔ No domain:* applied — routing is triage's call, but the work lands in packages/spec, which is the spec seat's sole ownership.

This is a contract addition, so it is escalated rather than adjudicated. It widens the authorable surface, which is the manual floor by the mechanical boundary test.

Why it exists

#8287 gave sys_api_key an active_organization_id column, named that way deliberately so the credential table would not become org-walled and start hiding keys from their own owners. #8707 is the recorded consequence: the audit writer must stamp a row with the organization of the record it is about, and it needs to know which column carries that.

PR #8777 solved the general case by resolving the column from the registered schema, mirroring SqlDriver.computeTenantField step for step. sys_api_key is the one shipped object that route cannot reach.

Measured, on origin/main

The underlying conflation: the platform has one answer to "what is this object walled by" and no answer to "which column says who this row is about". For ordinary objects those coincide; for credential tables they deliberately do not, which is why sys_api_key had to invent a differently-named column in the first place.

Options

四棱

实际业务需求 — measured, not speculative. The concrete scenario is revoking another user's API key while active in a different organization: the row lands behind the wrong tenant's wall, where the admin who can act on it cannot see it and one with no claim to the record can. sys_api_key declares enable.trackHistory: true, so the rows exist today.

平台长远合理性 — A separates two questions the platform currently conflates. B's long-term cost is a walled credential table, i.e. re-opening a closed defect. D leaves a declared-but-unenforced shape: the writer resolves the column from schema for every object except the one that motivated the work.

避免 AI 写代码犯错 — a declared key an author sets explicitly beats a consumer-side heuristic. The tempting alternative — scan for a lookup whose reference is sys_organization — is already falsified by a shipped object: sys_organization itself declares no organization_id and exactly one such lookup, parent_organization_id, so the scan would stamp every organization's audit rows with its parent's id. No gate would catch that. PR #8777 pins the falsification in a test.

创业阶段不扩散需求 — one shipped object needs this today, which is the pull that justifies widening a surface rather than anticipating one. Scope it to that: ⛔ not a general "field roles" mechanism.

四棱同向 (A) — which is why the recommendation is confident; it is escalated because contract additions are the manual floor regardless of alignment, not because the analysis is split.

Not a duplicate of #8707

#8707 stays open and carries this half; #8777's changeset says so. This card exists because the remaining work is in another lane#8707 is domain:identity and cannot be worked by that seat. Without a card in the spec lane, the half has no owner. Suggest Blocked-by: linkage from #8707 once this is routed.

A test in packages/plugins/plugin-audit/src/audit-writers.test.ts pins the current gap explicitly and is written to go red when this declaration lands, so the remaining half cannot be forgotten silently — but it will also need updating by whoever implements this.

Metadata

Metadata

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions