Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/20596-plugin-audit-provenance-anchors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/plugin-audit': patch
---

Provenance comments in `plugin-audit` were re-anchored

Comment and docblock lines under `src/` that cited tracker numbers which no
longer resolve on GitHub now cite the commit in this repository's history that
decided the matter, and say in their own words what was decided. Comments
only: no type, schema, export, log or refusal text, or runtime behaviour changes.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* red THERE. Breaking a writer is red here and green there. Neither file alone
* covers this object.
*
* ## 2026-08-24 — what the ruling on #11507 changed about this file
* ## 2026-08-24 — what the open-vocabulary ruling (commit 88b9d749a) changed about this file
*
* Nothing about the measurements; everything about what they MEAN. #8203 wrote
* §3 as "a defect, characterized", with the instruction to delete those cases
Expand Down Expand Up @@ -319,7 +319,7 @@ describe('[#8203/#11507] an author-contributed type is accepted — the open-voc
* instruction is RETIRED, and deleting them now would delete the only
* end-to-end measurement of a ruled contract.
*
* Maintainer ruling, 2026-08-24, #11507 (direction 4): `sys_activity.type` is
* Maintainer ruling, 2026-08-24, commit 88b9d749a (direction 4): `sys_activity.type` is
* an OPEN, author-extensible vocabulary. The declared options are the
* platform's built-in set; ADR-0052 §5b.2 `activityMilestones[].type` stays a
* sanctioned write path; an author-contributed value landing verbatim is what
Expand All @@ -329,7 +329,7 @@ describe('[#8203/#11507] an author-contributed type is accepted — the open-voc
* So a red here no longer reads "the fix landed". It reads: something has
* started REJECTING an author-contributed value — which is direction 3, a
* shipped authoring surface turned into a rejection path. Do not adapt these
* cases to it and do not weaken them; re-open #11507, because that is a
* cases to it and do not weaken them; re-open the ruling (commit 88b9d749a), because that is a
* maintainer call and not a test-fixing exercise.
*
* The mechanism is unchanged and still worth knowing: every field on this
Expand Down Expand Up @@ -362,7 +362,7 @@ describe('[#8203/#11507] an author-contributed type is accepted — the open-voc
* in the spec, so any metadata author can name any string and it lands. This
* is the authoring-time face of the open vocabulary, and the one an AI-written
* metadata app meets first — which is exactly why the declaration now says so
* in its own `description` (#11507), instead of showing that author a list
* in its own `description` (commit 88b9d749a), instead of showing that author a list
* that reads closed.
*/
it('a milestone declaring an undeclared type writes it — the authoring-surface hole', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
* gates bought a `driver.findOne` / matched-row read for a handler that was
* going to return on its first line.
*
* ## Why an allow list could not fix it (#5928, PR #6575)
* ## Why an allow list could not fix it (#5928, commit 69787f07b)
*
* `SKIP_OBJECTS` is a DENY list over an OPEN universe: `/meta` PUT registers new
* objects into a running engine with no event a plugin could subscribe to, so a
* registrant that enumerated the complement would freeze its list at boot and
* silently stop auditing everything created afterwards — a compliance
* regression, and a quiet one. The `excludeObjects` face #6575 added is the
* regression, and a quiet one. The `excludeObjects` face commit 69787f07b added is the
* expression this plugin was missing; `test 3` below is the pin that the deny
* direction is preserved.
*
Expand Down Expand Up @@ -462,7 +462,7 @@ describe('[#5860] the skip list is declared on the registration face', () => {
// invariant, and it is what this now asserts. An object-SCOPED gate costs
// the demand gate nothing beyond its own object — and on these two
// objects nothing at all: `comment-access-hooks.ts` (#4630) and
// service-storage's `attachment-access-hooks.ts` (#10091) already declare
// service-storage's `attachment-access-hooks.ts` (commit da891e0ef) already declare
// `beforeUpdate` scoped to `sys_comment` / `sys_attachment`, so
// `hasHooksFor` is already true for both wherever the access kits install.
const globalPreImage = registrations
Expand Down Expand Up @@ -524,7 +524,7 @@ describe('[#5860] the skip list is declared on the registration face', () => {
expect(excluded).toContain('sys_comment');
expect(excluded).toContain('sys_job_queue');
expect(excluded).not.toContain('biz_task');
// #6575 refuses both of these at registration; a spread of the real skip
// Commit 69787f07b refuses both of these at registration; a spread of the real skip
// list can never produce them, and this says so out loud.
expect(excluded).not.toContain('*');
expect(excluded.every((n) => typeof n === 'string' && n.trim().length > 0)).toBe(true);
Expand Down
14 changes: 7 additions & 7 deletions packages/plugins/plugin-audit/src/audit-writers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1208,7 +1208,7 @@ describe('audit writers — a lost audit row is reported at error (#5226)', () =
* keep losing rows for hours to a second fault with one `error` line at the
* top of the log describing the first;
* 2. that one line named the telemetry-datasource remedy unconditionally. The
* cause measured on #14927 was `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` and
* cause commit ab489388b records was `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` and
* the text said "datasource" — the operator was sent to check something
* that was not broken.
*
Expand Down Expand Up @@ -1367,7 +1367,7 @@ describe('audit writers — reported once per CAUSE, not once per process (#1516
expect(forMissingTable).toMatch(/telemetry/);
expect(forMissingTable).toMatch(/OS_TELEMETRY_DB=0/);

// The measured #14927 misdirection: the cause was an organization refusal
// The misdirection commit ab489388b records: the cause was an organization refusal
// and the text said "datasource".
const refused = makeCauseEngine(() => ORG_REQUIRED());
await refused.fire('crm_lead', 'l-1');
Expand Down Expand Up @@ -1407,7 +1407,7 @@ describe('audit writers — reported once per CAUSE, not once per process (#1516
});

/**
* [#8707] Which organization an audit row is stamped with — the RECORD'S own,
* [commit 1408fe385] Which organization an audit row is stamped with — the RECORD'S own,
* honouring the maintainer's ruling on #8287.
*
* The precedence these cases pin is `recordOrgId ?? sess.organizationId`. Read the
Expand Down Expand Up @@ -1645,12 +1645,12 @@ describe('audit writers — the record\'s own organization stamps the row (#8707
expect(stampOf(created).audit?.organization_id).not.toBe('org-parent');
});

// ── the platform stamp column — `sys_api_key` (#8778, #19054) ──────────
// ── the platform stamp column — `sys_api_key` (commit 7901b2dd2, #19054) ──
//
// The former ⛔ KNOWN GAP case lived here: it pinned that
// `sys_api_key.active_organization_id` was UNREACHABLE and stamped the
// ACTOR's org, and was written to go red the day the divergence became
// expressible. That day is #8778 (maintainer-ruled option A); the cases
// expressible. That day came with commit 7901b2dd2 (maintainer-ruled option A); the cases
// below are its rewrite, expecting `org-key`. #19054 moved the divergence
// off the authorable `tenancy.organizationField` key and into
// `PLATFORM_STAMP_ORGANIZATION_COLUMNS`, keyed by object name — so these
Expand Down Expand Up @@ -1879,7 +1879,7 @@ describe('audit writers — the writer reads the session key the engine emits (#

// This case is GREEN before and after the #9516 fix on purpose: it pins
// that fixing WHICH KEY the fallback arm reads did not disturb the ORDER
// the #8707 ruling set (honouring the maintainer's ruling on #8287).
// commit 1408fe385 set (honouring the maintainer's ruling on #8287).
await fire('afterInsert', {
object: 'crm_lead',
input: { id: 'lead-1' },
Expand Down Expand Up @@ -1919,7 +1919,7 @@ describe('audit writers — the writer reads the session key the engine emits (#
// Lower stakes than the audit stamp — it feeds `resolveWriteLocale` and the
// emitted envelope's `organizationId` rather than a row behind an RLS wall —
// but the same removed key, dead the same way. Note the ORDER here is
// session-first and stays that way: #8707's ruling reasons about an AUDIT
// session-first and stays that way: commit 1408fe385 reasons about an AUDIT
// ROW read through the record's own tenant wall, which is not what a mention
// notification is. Only the key changes at this site.
const setupMentions = (schemas: Record<string, string[] | Record<string, any>> = SINGLE_TENANT) => {
Expand Down
18 changes: 9 additions & 9 deletions packages/plugins/plugin-audit/src/audit-writers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { SECRET_MASK, collectMaskedReadFields } from '@objectstack/objectql/core
// picker, the search companion and the approval inbox the day an author sets
// `nameField` — the same argument the SECRET_MASK import above makes.
import { referenceTargetOf, resolveDisplayField } from '@objectstack/spec/data';
// [#8707 / #10101] The platform-row organization resolver, imported rather
// than owned. It started life in THIS file (#8707, honouring #8287's ruling)
// [commit 1408fe385 / #10101] The platform-row organization resolver, imported rather
// than owned. It started life in THIS file (commit 1408fe385, honouring #8287's ruling)
// and was promoted to `@objectstack/metadata-core` by the maintainer ruling
// recorded on cloud#1395: ONE shared resolver for all three platform-row
// writers (audit, approvals, automation runs) — a per-writer copy of the
Expand Down Expand Up @@ -190,7 +190,7 @@ const SKIP_OBJECTS = new Set<string>([
* #5038's bulk equivalents) had to answer "yes, a hook covers this object" for
* every one of these tables and buy a row read for a handler that returns on
* its first line. The knowledge existed; the contract had nowhere to put it
* until #5928 / PR #6575 added `excludeObjects`.
* until #5928 / commit 69787f07b added `excludeObjects`.
*
* Why the negative face and not `object: [...]` with the complement: the object
* universe is OPEN. `/meta` PUT registers new objects into a running engine and
Expand Down Expand Up @@ -223,7 +223,7 @@ const NOISE_FIELDS = new Set<string>([
]);

/**
* [#8144 / #8707 / #10101] `createFieldPresenceProbe` and
* [#8144 / commit 1408fe385 / #10101] `createFieldPresenceProbe` and
* `resolveRecordOrganizationField` were defined HERE until #10101 promoted
* them to `@objectstack/metadata-core` (the cloud#1395 ruling: one shared
* platform-row organization resolver for audit, approvals and automation
Expand Down Expand Up @@ -786,7 +786,7 @@ function renderMilestoneSummary(
* puts a CLOSED vocabulary there — SQLSTATE (`42P01`, `23505`), mysql2's
* symbolic names (`ER_NO_SUCH_TABLE`), SQLite's `SQLITE_*` — and ADR-0112 does
* the same for the engine's own refusals (`ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED`,
* the cause measured on #14927). None of them varies per row.
* the cause commit ab489388b records). None of them varies per row.
*
* So the key is bounded by two sets fixed at BOOT — the declared object
* registry and the driver's code vocabulary — and by nothing that grows with
Expand Down Expand Up @@ -1030,7 +1030,7 @@ export function installAuditWriters(
return def;
};

// [#8707 / #10101] The object's own organization COLUMN and value, through
// [commit 1408fe385 / #10101] The object's own organization COLUMN and value, through
// the SHARED platform-row resolver (`@objectstack/metadata-core`) — one
// memoized instance per installation, the same instance shape the approval
// writer and the automation-run recorder hold. See
Expand Down Expand Up @@ -1347,7 +1347,7 @@ export function installAuditWriters(
// a strict sys_user lookup); the service principal lands on `actor`.
const actorLabel: string | null =
userId ?? (typeof sess.actor === 'string' && sess.actor.trim() ? sess.actor.trim() : null);
// [#8707, honouring #8287's ruling] The audited RECORD'S OWN organization
// [commit 1408fe385, honouring #8287's ruling] The audited RECORD'S OWN organization
// wins; the acting session's active organization is the fallback. ⛔ Do not
// flip this back to `sess.organizationId ?? recordOrgId`.
//
Expand Down Expand Up @@ -1399,7 +1399,7 @@ export function installAuditWriters(
// `readonly: true` (so `validateRecord` skips them) and this whole path is
// wrapped in swallow-and-report.
//
// It survived a careful review of exactly these lines because #8707
// It survived a careful review of exactly these lines because commit 1408fe385
// reordered the two arms without evaluating either one: reordering two
// expressions does not tell you whether they resolve. The pins in
// `audit-writers.test.ts` (#9516 block) are what check this comment against
Expand Down Expand Up @@ -1689,7 +1689,7 @@ export function installAuditWriters(
* the declaration whether or not a creation happened. On insert only, a
* caller barred from *creating* an attachment on a `files: false` object
* could *move* an existing one onto it. `attachment-access-hooks.ts`
* authorizes the re-point (#10091: the new `parent_object`/`parent_id`
* authorizes the re-point (commit da891e0ef: the new `parent_object`/`parent_id`
* must be editable) — access, again, not capability.
*/
const enforceFilesCapability = async (ctx: HookContext) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* *move* an existing one onto it, and a `sys_comment` could be re-threaded
* into a feeds-disabled object's thread. The access kits authorize the
* re-point (`comment-access-hooks.ts` since #4630,
* `attachment-access-hooks.ts` since #10091) — those are ACCESS checks, and
* `attachment-access-hooks.ts` since commit da891e0ef) — those are ACCESS checks, and
* the capability half was never asked on the update verb.
*
* This file runs against a REAL `ObjectQL` (a stub driver underneath), not the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ describe('comment access — beforeDelete (author or parent editor)', () => {
).rejects.toMatchObject({ code: 'RECORD_NOT_ACCESSIBLE' });
});

// [#9798] The UNSCOPED multi-write block that used to sit here called the
// [commit c7655d472] The UNSCOPED multi-write block that used to sit here called the
// handlers DIRECTLY with a whole-operation context of this file's own
// construction — a shape the engine's per-row dispatch (#5038/#5574) never
// produced on either verb, so it stayed green for a behaviour the wired
Expand Down Expand Up @@ -401,8 +401,8 @@ describe('#7141 — caller envelope forwarded to the sharing gate', () => {
await beforeDelete(envelopeWriteCtx('beforeDelete', { id: 'c1' }, { ...DELEGATED_ENVELOPE }));

const forwarded = canEdit.mock.calls[0]![2] as unknown as Record<string, unknown>;
// Every principal field survives — the #6523 contract's unit is the envelope
// and #6206 forbids rebuilding a subset of it.
// Every principal field survives — the unit of commit aa4b90d9a's contract is the envelope
// and the full-envelope ruling forbids rebuilding a subset of it.
expect(forwarded).toEqual({
userId: 'human_1',
tenantId: 'org_1',
Expand Down Expand Up @@ -534,7 +534,7 @@ describe('#7141 — caller envelope forwarded to the sharing gate', () => {
});

// ─────────────────────────────────────────────────────────────────────────
// #4630's unscoped multi-write refusals through the WIRED engine (#9798)
// #4630's unscoped multi-write refusals through the WIRED engine (commit c7655d472)
//
// A real `ObjectQL` + in-memory driver + this module's installer — the exact
// path `ql.delete('sys_comment', …)` / `ql.update('sys_comment', …)` take in
Expand Down
12 changes: 6 additions & 6 deletions packages/plugins/plugin-audit/src/comment-access-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* carrying NEITHER an id NOR a `where` is refused outright rather than
* authorizing the whole table by resolving zero rows — on BOTH verbs that
* refusal reaches this handler through the `dispatchUnscopedMultiWrite`
* whole-operation dispatch both registrations declare (#9719/#9798 built
* whole-operation dispatch both registrations declare (#9719/commit c7655d472 built
* it for delete; #9974 ruled it onto update).
* - {@link installCommentReadVisibility} — the read side: a
* `find`/`findOne`/`count`/`aggregate` middleware that intersects the query
Expand Down Expand Up @@ -74,7 +74,7 @@ export interface CommentAccessEngine {
options?: {
object?: string;
packageId?: string;
/** [#9798, both verbs since #9974] Opt-in whole-operation `beforeUpdate` /
/** [commit c7655d472, both verbs since #9974] Opt-in whole-operation `beforeUpdate` /
* `beforeDelete` dispatch for an UNSCOPED predicate write
* (`multi: true`, no `where`) — the engine declaration added by #9719.
* Declared here because this file's registrations pass it; the mechanism,
Expand Down Expand Up @@ -219,7 +219,7 @@ function asIdList(id: unknown): Array<string | number> | null {
* snapshot lacks `permissions`, which sharing bypasses need.
*
* [#7136] Typed as the full envelope, which is what `ISharingService` declares
* for every parameter this value is handed to (#6523 / the #6206 ruling).
* for every parameter this value is handed to (commit aa4b90d9a / the full-envelope ruling).
*
* [#7141] And FORWARDED as the full envelope, which is the other half of that
* ruling: a caller "MUST NOT rebuild a subset of it". The five-field projection
Expand Down Expand Up @@ -369,7 +369,7 @@ export function installCommentAccessHooks(
// authorize row-by-row, and "nothing to authorize" must never read as
// "allowed" (the engine would hand an unscoped AST to deleteMany).
//
// [#9798/#9974] This branch is verb-neutral in what it says AND, since
// [commit c7655d472/#9974] This branch is verb-neutral in what it says AND, since
// #9974, in what reaches it. On BOTH verbs the only dispatch that can
// deliver this shape is the `dispatchUnscopedMultiWrite` whole-operation
// dispatch this module's two `before*` registrations declare (see below):
Expand Down Expand Up @@ -472,7 +472,7 @@ export function installCommentAccessHooks(
}
},
// [#9974] `dispatchUnscopedMultiWrite` is what makes the #4630 unscoped
// refusal in `resolveTargetRows` REACHABLE ON UPDATE — the half #9798 could
// refusal in `resolveTargetRows` REACHABLE ON UPDATE — the half commit c7655d472 could
// not land, ruled in on 2026-08-19. Without it the predicate path dispatches
// per row with `input.id` bound (so the by-id branch shadows the shape
// check) and a zero-match predicate dispatches nothing, leaving the declared
Expand All @@ -491,7 +491,7 @@ export function installCommentAccessHooks(
if (!rows.length) return; // nothing matched — nothing to authorize
await authorizeRows(ctx, rows, 'delete');
},
// [#9798] `dispatchUnscopedMultiWrite` is what makes the #4630 unscoped
// [commit c7655d472] `dispatchUnscopedMultiWrite` is what makes the #4630 unscoped
// refusal in `resolveTargetRows` REACHABLE through the wired engine: the
// predicate path dispatches per row with `input.id` bound (so the by-id
// branch shadows the check), and a zero-match predicate dispatches nothing
Expand Down
Loading
Loading