Skip to content

Commit a3d47c2

Browse files
fix(metadata-fs, runtime): an actor-less filesystem write records null, never the label fs; the dispatcher metadata PUT records the user (#23109)
Part of #23086 Clause-②: no This PR covers positions 2 and 3 of the `PutOptions.actor` producer family. The contract (`packages/metadata-core/src/types.ts`, #4556) is the acting user's id, or `null`, and never a label. It follows the triage direction in comment 6131584326: positions 2 and 3 in one PR, one pin per position, no second resolver, and no new repo-wide check. Position 1 (plugin-security's `'system'`) is not edited here. The card stays open so the seat can verify position 1 when #22975's 7a and 7b land. ## Measured first, on `origin/main` `6662e3f590` - **Position 1, confirmed and not edited.** Both modules still pass the label. `permission-set-projection.ts:1658` has `actor: 'system'`, and so does `position-environment-backfill.ts:299`. The same two hits are there at `9956048b4c` and at `origin/main` `8464380248`. - **Position 2: where `'fs'` lands.** `FileSystemRepository`'s constructor set `this.fsActor = opts.fsActor ?? 'fs'`, and `fsActor` reached four places: - the event `actor` of an edit seen on disk (`handleFsChange`, `publishExternalDelete`); - `authoredBy` in `get()`, in the `list()` header, and in a no-op `put()`, each spelled `meta?.actor ?? this.fsActor`. `??` treats a recorded `null` as absent. So a `put()` with `actor: null` read back from `get()` / `list()` as authored by `'fs'`, even though the write and its log record held `null`. The new pin read exactly that on the unfixed source: `expected { get: 'fs', list: [ 'fs' ], … } to deeply equal { get: null, list: [ null ], … }`. - **`MetadataPlugin` never passes `fsActor`.** `packages/metadata/src/plugin.ts` constructs the repository with `root`, `org` and `disableWatch` only, so every host that boots the plugin inherited the label. - **Readers of `authoredBy` and the event actor from this repository: none key on `'fs'`.** - `MetadataManager.applyRepoEvent` reads `op`, `ref`, `ts` and `seq`, and never `actor`. - `MetadataManager.getRepository()` has no non-test caller. - No HTTP route exposes this repository's history or items, and the HMR legacy events carry no actor, so no Studio provenance display can read it. - `LayeredRepository` is never constructed over it outside tests. - In non-test `packages/**/*.ts`, a `git grep` finds no comparison of an actor or `authoredBy` with `'fs'`. - The only readers keyed on the label were nine assertions in `metadata-fs`'s own watcher suites (`expect(… .actor).toBe('fs')`). They are updated below. - `fsActor` itself has no producer anywhere in this tree. - **Position 3.** `HttpDispatcher`'s `PUT /metadata/:type/:name` branch called `saveMetaItem` with no `actor`, although the execution context it had just admitted carries `userId`. A signed-in author's save through the `@objectstack/hono` catch-all therefore recorded `null` in `sys_metadata.updated_by` / `created_by` and `sys_metadata_history.recorded_by`. The new pin read exactly that on the unfixed branch: `expected { created_by: null, … } to deeply equal { created_by: 'u_author', … }`. `RestServer`'s twin records the user through `resolveMetaWriteActor`. ## Change - `packages/metadata-fs/src/repository.ts`: - `fsActor` defaults to `null`, and its exported type (`fsActor?: string`) is unchanged. Its doc now states the rule: a user id for a change the repository did not write itself, `null` when omitted, never a label. `source: 'fs'` already names the origin on every event. - The three `authoredBy` reads go through one private `authoredByOf(meta)`. When a log record exists, it answers with that record's actor, and a recorded `null` stays `null`. Only when no record exists (a file placed on disk outside the repository) does it answer with `fsActor`. - `packages/metadata/src/plugin.ts`: a comment at the attach states the actor rule, as the explicit-composition rule asks. The code is unchanged. With the default gone, the attach gives `null`. - `packages/runtime/src/domains/meta.ts`: the `PUT` branch passes `executionContext.userId` when it is a non-empty string, and nothing otherwise. That is the same inline reading the `_migrate-stored` branch got in `8c10776152`, with no new resolver function. The actor is read off the admitted identity only, never off `item` or a header. - `.changeset/23086-actor-family.md`: `patch` for `@objectstack/metadata-fs` and `@objectstack/runtime`, written for an operator. `@objectstack/metadata` is not listed because its only change is a `//` comment, and no comment ships. Built `packages/metadata/dist` holds zero hits for the new comment and zero for the pre-existing comment above it, in js, cjs and maps. The positive control, the runtime string `FileSystemRepository attached`, hits `dist/index.js` once. ## Pins (one per position, plus the control) - **Position 2, the repository:** `packages/metadata-fs/test/actor-never-a-label.test.ts` (new). It checks the `put()` receipt, `get()`, the `list()` header and the `history()` event: - a write with no actor records `null` on every surface, a no-op rewrite included; - a write by `usr_alice` records `usr_alice` on every surface; - a file no recorded write produced reports `null`; - a named `fsActor` answers only where no write is recorded, and a recorded `null` stays `null`. The nine watcher-suite assertions now read `actor` as `null` for an edit seen on disk (`source` still `'fs'`). - **Position 2, the `MetadataPlugin` door:** `packages/metadata/src/plugin-fs-repository-actor.test.ts` (new). A real `LiteKernel` boots a real `MetadataPlugin`, and the test reads the repository handle it attached. A file placed under `.objectstack/metadata` before boot reports `null`, and so does an actor-less `put()`. The positive control is that the placed file was indexed. If a future attach passes a label as `fsActor`, this test turns red. - **Position 3, the dispatcher door:** `packages/runtime/src/domains/meta-save-actor.test.ts` (new). Each door gets its own real store: better-sqlite3 `:memory:`, the real `sys_metadata*` objects, and a real `ObjectStackProtocolImplementation`. The test reads the rows back after every save: - a signed-in `u_author` save records `u_author` in `created_by`, `updated_by` and `recorded_by`; - a later save by `u_editor` moves `updated_by` to `u_editor`, keeps `created_by`, and appends `u_editor` to `recorded_by`; - an `isSystem` save with no user records `null` in all three. An anonymous caller never reaches the save, because the anonymous deny answers first, so "nobody" is the `isSystem` caller. - **CONTROL:** the same two cases run against `RestServer`'s `PUT` twin (`resolveMetaWriteActor`), which is unchanged and records the same rows. - `packages/runtime/src/http-dispatcher.test.ts`: the deliberate exact-match pin of the dispatcher's `saveMetaItem` request now includes `actor: 'u1'`. It was the one red in the full runtime run before that update. ## Ablation The fix and its pins were committed first. Each leg went through `scripts/ablation-replace.mjs` in WRAP mode, which carries its own restore trap. The legs ran inside one script that also carried an `EXIT INT TERM` trap restoring both files from `HEAD` by absolute path. The run was at `1f9c123dcf`. - **Leg A1, the `'fs'` default restored** (`opts.fsActor ?? null` back to `opts.fsActor ?? 'fs'`): - The anchor fell from 1 to 0 and the blob went from `f76f6dee4f48` to `bfde62909356`. - `metadata-fs` was rebuilt, and `ablation-dist-preflight.mjs` found the marker in 2 built files. The pristine witness had found the fixed spelling in dist before the mutation. - Result: `metadata-fs` 11 failed and 63 passed. The failures were the new pin's file-with-no-record case and the ten watcher tests behind the nine updated assertions (`expected 'fs' to be null`). The new pin's other three cases stayed green. - The plugin door pin failed 1 of 1 (`expected 'fs' to be null`). The runtime pin passed 4 of 4. - Restore leg: rebuilt, and the preflight found the marker absent from all 6 built files with the tree clean. - **Leg A2, the `meta?.actor ?? this.fsActor` fallback restored, with the default left `null`:** - The anchor fell from 1 to 0 and the blob went from `f76f6dee4f48` to `c50311af3a10`. - Result: `metadata-fs` 1 failed and 73 passed. The failure was the named-`fsActor` case, `expected { get: 'usr_operator', … } to deeply equal { get: null, … }`. The runtime pin passed 4 of 4. - The plugin pin was not run in this leg: its subject resolves through `metadata-fs`'s `dist/`, which this leg did not rebuild, so its reading would have proved nothing. - **Leg B, the dispatcher's `userId` dropped** (`const actor: string | undefined = undefined;`): - The anchor fell from 1 to 0 and the blob went from `84a24c33019f` to `5632faa44578`. - Result: the runtime pin had 1 failed and 3 passed. The failure was the dispatcher's signed-in case, `expected { created_by: null, … } to deeply equal { created_by: 'u_author', … }`. The dispatcher's `isSystem` case and both `RestServer` control cases stayed green. - The `metadata-fs` pin (4/4) and the plugin pin (1/1) stayed green. - **Restore:** both blobs equal `HEAD` (`f76f6dee4f48`, `84a24c33019f`). `git diff HEAD` is empty and `git status --porcelain` is empty. ## Verification `origin/main` `55828c5145` was merged once, at `9956048b4c`. The runtime and metadata-fs changes are disjoint from it. After the merge I ran `pnpm install --frozen-lockfile` and `turbo run build --filter=!@objectstack/docs` (73/73 tasks). - At `9956048b4c`: - `@objectstack/metadata-fs` `pnpm test`: 11 files, 74 tests, exit 0. - `@objectstack/metadata` `pnpm test`: 61 files, 880 tests, exit 0. - `@objectstack/runtime` `--project local` over `src/http-dispatcher.test.ts` and `src/domains/`: 110 files, 2299 tests, exit 0. - `typecheck` for `metadata-fs`, `metadata` and `runtime`: exit 0. Runtime's test-typecheck ledger is unchanged (27 files, 190 errors, 68 pinned signatures). - Before the merge, at `f489836584`, `@objectstack/runtime`'s full `--project local` suite ran 362 files: 361 passed, and 1 failed. The failure was the exact-match pin in `http-dispatcher.test.ts`, updated in `1f9c123dcf`. 5166 tests passed and 19 were skipped. The full runtime suite was not re-run after the merge. The incoming commits touch `metadata-protocol`'s envelope and install-lock paths, not the save actor path, so only the meta domain, the dispatcher file and the pins were re-run. The merge ref's full run is CI's. - **Gates, at the final head `9956048b4c`:** `dispatch-gates --commands` derived 66 commands on the real diff. That is the 55-command lead list plus 11 changeset-family commands. Each exit code was captured before any pipe, and all 66 exited 0. `dispatch-gates --ran` reports 66 derived, 66 run, 0 NOT-MEASURED and 0 UNRUN. - `check:dual-build-cjs-loads` read real built output after a full build (107 entries, 66 packages). - `check:query-options-erasure` is back at its 236-site test ceiling. - The derivation warns that `origin/main` has since moved by 2 commits, which changed three engine-double roster baselines. This diff adds no engine double. - **Lint, a proven narrowing:** `eslint --no-inline-config --format json` over the 14 changed `.ts` files gives 14 files, 0 errors, 0 warnings and 0 ignore notices, so all 14 were linted. `eslint.config.mjs` never enables type-aware linting (its own comment at line 327: no `parserOptions.project`, no typed `@typescript-eslint` rules), so this diff cannot change the verdict on any file it does not touch. The repository-wide `pnpm lint` is left to CI. ## Acceptance notes - Entries written before this release keep what they recorded: `'fs'` in an existing `.objectstack/.log/main.jsonl`, and `null` in dispatcher-saved rows. This is per the card's "Not here". - `fsActor` has no producer in this tree. Retiring it would narrow an exported options type (Clause-② narrowing). It is left in place with its type unchanged, and only its default moved. No card is filed: per the family rule, this is an observation and not a defect. - `RestServer.resolveMetaWriteActor`'s docblock (`packages/rest/src/rest-server.ts`) still says "the protocol's `'system'` / `NULL` defaults still apply". The `'system'` half has been stale since #4556. The file is outside this claim, and nobody is carrying it. - The dispatcher `PUT`'s fallback, `metadata.saveItem(type, name, item)`, carries no actor either. No metadata service in this tree implements `saveItem` (the only hits are the client SDK's `meta.saveItem`), so the branch is unreachable with the in-tree `MetadataManager`. This is a dead-code observation, not filed. - A dispatcher save by a signed-in user now also gets that user in `sys_metadata_audit.actor` (through `recordMetadataAudit`) instead of that column's `'system'` default, the same as on the REST door. - Comments in `metadata-fs`'s `self-write-suppression.test.ts` that quote the pre-fix symptom `{op:'update', actor:'fs'}` are left as the historical record of what was measured. --- _Generated by [Claude Code](https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 240890c commit a3d47c2

15 files changed

Lines changed: 456 additions & 15 deletions

‎.changeset/23086-actor-family.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/metadata-fs': patch
3+
'@objectstack/runtime': patch
4+
---
5+
6+
fix(metadata-fs, runtime): metadata writes record the acting user's id or nobody, never the label `'fs'`, and a signed-in save through the runtime dispatcher records the user
7+
8+
Clause-②: no
9+
10+
- **What was wrong (filesystem repository).** `FileSystemRepository` named the label `'fs'` as the writer whenever no user was given. That covered every edit it saw on disk, every file it found that no recorded write produced, and an actor-less `put()`, which read back as `'fs'`. `MetadataPlugin` attaches the repository without naming a user, so this happened on every host that boots it. The contract for these fields (`PutOptions.actor`, `MetadataItem.authoredBy`, `MetadataEvent.actor`) is the acting user's id, or `null` for "nobody".
11+
- **What it does now.** With no user named, the repository records `null`. A write that names a user records that user, and a write that records `null` reads back as `null`. The `fsActor` option keeps its type. When it is set, it is the user id reported for changes the repository did not write itself, and it no longer replaces a writer that a write recorded. Every event still names its origin on `source: 'fs'`, as before.
12+
- **What was wrong (runtime dispatcher).** `PUT /api/v1/meta/:type/:name` served by the runtime dispatcher (the `@objectstack/hono` catch-all) saved without the caller's identity, so a signed-in author's save recorded `null` in `sys_metadata.updated_by` / `created_by` and `sys_metadata_history.recorded_by`. `RestServer`'s route recorded the user.
13+
- **What an operator sees.** Filesystem-backed metadata history and events from this release on no longer name `'fs'` as the writer. A save through the runtime dispatcher records the signed-in user's id, as the REST route does, and a system caller with no user records `null`. Entries written before this release keep what they recorded.

‎packages/metadata-fs/src/repository.ts‎

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,17 @@ export interface FileSystemRepositoryOptions {
6363
root: string;
6464
/** Tenant/org. */
6565
org: string;
66-
/** Identity reported in events that originate from external FS edits. */
66+
/**
67+
* The acting user id to report for a change this repository did not write
68+
* itself: an edit observed on disk (a watcher or resync event), and a file
69+
* with no recorded write behind it (its `authoredBy`).
70+
*
71+
* Omitted → `null`, "no actor": the `PutOptions.actor` contract (#4556) is
72+
* the acting user's id or `null`, ⛔ never a label such as `'fs'`. Every
73+
* event already names its origin as `source: 'fs'`. A write that came
74+
* through `put()` / `delete()` reports its own `actor`, `null` included,
75+
* and this value never replaces it.
76+
*/
6777
fsActor?: string;
6878
/** Disable chokidar watcher (e.g. for read-only contexts). */
6979
disableWatch?: boolean;
@@ -112,7 +122,8 @@ const isEnoent = (err: unknown): boolean =>
112122
export class FileSystemRepository implements MetadataRepository {
113123
private readonly layout: FsLayout;
114124
private readonly org: string;
115-
private readonly fsActor: string;
125+
/** See {@link FileSystemRepositoryOptions.fsActor}: a user id, or `null`. */
126+
private readonly fsActor: string | null;
116127
private readonly disableWatch: boolean;
117128
private readonly now: () => Date;
118129
private readonly log: JsonlLog;
@@ -147,7 +158,8 @@ export class FileSystemRepository implements MetadataRepository {
147158

148159
constructor(opts: FileSystemRepositoryOptions) {
149160
this.org = opts.org;
150-
this.fsActor = opts.fsActor ?? 'fs';
161+
// [#23086] No default label: with no user named, the answer is `null`.
162+
this.fsActor = opts.fsActor ?? null;
151163
this.disableWatch = opts.disableWatch ?? false;
152164
this.now = opts.now ?? (() => new Date());
153165
this.layout = { root: path.resolve(opts.root) };
@@ -269,7 +281,7 @@ export class FileSystemRepository implements MetadataRepository {
269281
body: body as Record<string, unknown>,
270282
hash,
271283
parentHash: meta?.parentHash ?? null,
272-
authoredBy: meta?.actor ?? this.fsActor,
284+
authoredBy: this.authoredByOf(meta),
273285
authoredAt: meta?.ts ?? new Date(0).toISOString(),
274286
message: meta?.message,
275287
seq: meta?.seq ?? 0,
@@ -298,7 +310,7 @@ export class FileSystemRepository implements MetadataRepository {
298310
ref: { ...ref, version: undefined },
299311
hash,
300312
parentHash: meta?.parentHash ?? null,
301-
authoredBy: meta?.actor ?? this.fsActor,
313+
authoredBy: this.authoredByOf(meta),
302314
authoredAt: meta?.ts ?? new Date(0).toISOString(),
303315
message: meta?.message,
304316
seq: meta?.seq ?? 0,
@@ -372,7 +384,7 @@ export class FileSystemRepository implements MetadataRepository {
372384
body: spec as Record<string, unknown>,
373385
hash,
374386
parentHash: meta?.parentHash ?? null,
375-
authoredBy: meta?.actor ?? this.fsActor,
387+
authoredBy: this.authoredByOf(meta),
376388
authoredAt: meta?.ts ?? this.now().toISOString(),
377389
message: meta?.message,
378390
seq: meta?.seq ?? 0,
@@ -534,6 +546,20 @@ export class FileSystemRepository implements MetadataRepository {
534546
return last;
535547
}
536548

549+
/**
550+
* `authoredBy` for a HEAD, read off the log record that produced it.
551+
*
552+
* [#23086] A record answers with the actor it recorded, and a recorded `null`
553+
* stays `null`: `meta?.actor ?? fallback` read a `put()` with `actor: null`
554+
* back as the fallback, so an actor-less write came back authored by the
555+
* `'fs'` default. The fallback answers only when no record exists (a file
556+
* placed on disk outside this repository), and it is `fsActor`, which is a
557+
* user id or `null`.
558+
*/
559+
private authoredByOf(meta: MetadataEvent | null): string | null {
560+
return meta ? (meta.actor ?? null) : this.fsActor;
561+
}
562+
537563
/**
538564
* Register a path this repository just wrote with the watcher (#7282).
539565
*
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#23086] `FileSystemRepository` reports the acting user's id, or `null` —
5+
* never the label `'fs'`.
6+
*
7+
* The contract is `PutOptions.actor` (`@objectstack/metadata-core`, #4556):
8+
* the acting user id, or `null` for a write with no actor. `authoredBy` and
9+
* `MetadataEvent.actor` carry the same rule. The repository broke it twice:
10+
*
11+
* - its constructor defaulted `fsActor` to `'fs'`, and `MetadataPlugin`
12+
* attaches it without one, so every edit observed on disk and every file
13+
* with no recorded write reported the label;
14+
* - every `authoredBy` read was `meta?.actor ?? fsActor`, and `??` treats a
15+
* recorded `null` as absent, so a `put()` with `actor: null` read back as
16+
* authored by the fallback.
17+
*
18+
* The origin stays named, on `source: 'fs'`. These cases read every surface a
19+
* caller sees: the `put()` receipt, `get()`, the `list()` header and the
20+
* `history()` event. The watcher face (an edit made on disk) is pinned by the
21+
* `actor` assertions in this package's watcher suites.
22+
*/
23+
24+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
25+
import fs from 'node:fs/promises';
26+
import path from 'node:path';
27+
import os from 'node:os';
28+
import type { MetaRef, MetadataEvent, MetadataItemHeader } from '@objectstack/metadata-core';
29+
import { FileSystemRepository } from '../src/index.js';
30+
31+
const ref = (name: string): MetaRef => ({ org: 'system', type: 'view', name });
32+
33+
async function collect<T>(it: AsyncIterable<T>): Promise<T[]> {
34+
const out: T[] = [];
35+
for await (const x of it) out.push(x);
36+
return out;
37+
}
38+
39+
describe('[#23086] FileSystemRepository: the acting user id, or null, never the label "fs"', () => {
40+
let root: string;
41+
let repo: FileSystemRepository | undefined;
42+
43+
beforeEach(async () => {
44+
root = await fs.mkdtemp(path.join(os.tmpdir(), 'objectstack-fs-actor-'));
45+
});
46+
47+
afterEach(async () => {
48+
if (repo) await repo.close().catch(() => undefined);
49+
repo = undefined;
50+
await fs.rm(root, { recursive: true, force: true });
51+
});
52+
53+
/** What every read surface reports as the writer of `name`'s HEAD. */
54+
async function writerSeenBy(r: FileSystemRepository, name: string) {
55+
const got = await r.get(ref(name));
56+
const headers: MetadataItemHeader[] = await collect(r.list({ org: 'system', type: 'view', nameContains: name }));
57+
const events: MetadataEvent[] = await collect(r.history(ref(name)));
58+
return {
59+
get: got?.authoredBy,
60+
list: headers.map((h) => h.authoredBy),
61+
history: events.map((e) => e.actor),
62+
};
63+
}
64+
65+
it('a write with no actor records null on every surface, never "fs"', async () => {
66+
repo = new FileSystemRepository({ root, org: 'system', disableWatch: true });
67+
await repo.start();
68+
const put = await repo.put(ref('nobody'), { label: 'v1' }, { parentVersion: null, actor: null });
69+
expect(put.item.authoredBy).toBeNull();
70+
expect(await writerSeenBy(repo, 'nobody')).toEqual({ get: null, list: [null], history: [null] });
71+
72+
// A no-op write (same content) reads the recorded writer back too.
73+
const again = await repo.put(ref('nobody'), { label: 'v1' }, { parentVersion: put.version, actor: null });
74+
expect(again.item.authoredBy).toBeNull();
75+
});
76+
77+
it('a write with an actor records that actor on every surface', async () => {
78+
repo = new FileSystemRepository({ root, org: 'system', disableWatch: true });
79+
await repo.start();
80+
const put = await repo.put(ref('mine'), { label: 'v1' }, { parentVersion: null, actor: 'usr_alice' });
81+
expect(put.item.authoredBy).toBe('usr_alice');
82+
expect(await writerSeenBy(repo, 'mine')).toEqual({
83+
get: 'usr_alice', list: ['usr_alice'], history: ['usr_alice'],
84+
});
85+
});
86+
87+
it('a file no recorded write produced reports null as its writer', async () => {
88+
// Placed on disk before `start()`, as an editor or a checkout leaves it:
89+
// the boot scan indexes it, and the log holds no record for it.
90+
await fs.mkdir(path.join(root, 'view'), { recursive: true });
91+
await fs.writeFile(path.join(root, 'view', 'placed.json'), JSON.stringify({ label: 'from disk' }));
92+
repo = new FileSystemRepository({ root, org: 'system', disableWatch: true });
93+
await repo.start();
94+
expect(await writerSeenBy(repo, 'placed')).toEqual({ get: null, list: [null], history: [] });
95+
});
96+
97+
it('a named fsActor answers only where no write is recorded; a recorded null stays null', async () => {
98+
await fs.mkdir(path.join(root, 'view'), { recursive: true });
99+
await fs.writeFile(path.join(root, 'view', 'placed.json'), JSON.stringify({ label: 'from disk' }));
100+
repo = new FileSystemRepository({ root, org: 'system', disableWatch: true, fsActor: 'usr_operator' });
101+
await repo.start();
102+
expect((await repo.get(ref('placed')))?.authoredBy).toBe('usr_operator');
103+
104+
await repo.put(ref('nobody'), { label: 'v1' }, { parentVersion: null, actor: null });
105+
expect(await writerSeenBy(repo, 'nobody')).toEqual({ get: null, list: [null], history: [null] });
106+
});
107+
});

‎packages/metadata-fs/test/external-delete-requires-absence.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe('#7369 an external delete is published only for an item that is really
167167
expect(events[0]!.op).toBe('update');
168168
expect(events[0]!.ref.name).toBe('case_grid');
169169
expect(events[0]!.source).toBe('fs');
170-
expect(events[0]!.actor).toBe('fs');
170+
expect(events[0]!.actor).toBeNull();
171171
expect(events[0]!.parentHash).toBe(first.version);
172172
expect(events[0]!.hash).not.toBeNull();
173173

@@ -238,7 +238,7 @@ describe('#7369 an external delete is published only for an item that is really
238238
expect(events[0]!.hash).toBeNull();
239239
expect(events[0]!.parentHash).toBe(first.version);
240240
expect(events[0]!.source).toBe('fs');
241-
expect(events[0]!.actor).toBe('fs');
241+
expect(events[0]!.actor).toBeNull();
242242

243243
expect(await repo!.get(r)).toBeNull();
244244
});

‎packages/metadata-fs/test/external-write-resync.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ describe('FileSystemRepository — external writes survive a lost watcher event
168168
// The backstop must be indistinguishable from the fast path: a subscriber
169169
// cannot be made to care which one noticed.
170170
expect(created[0]!.source).toBe('fs');
171-
expect(created[0]!.actor).toBe('fs');
171+
expect(created[0]!.actor).toBeNull();
172172
expect(created[0]!.parentHash).toBeNull();
173173

174174
// ── update ───────────────────────────────────────────────────────────

‎packages/metadata-fs/test/fs-behavior.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ describe('FileSystemRepository — on-disk semantics', () => {
128128
expect(collected).toHaveLength(1);
129129
expect(collected[0]!.op).toBe('update');
130130
expect(collected[0]!.source).toBe('fs');
131-
expect(collected[0]!.actor).toBe('fs');
131+
expect(collected[0]!.actor).toBeNull();
132132
}, WATCHER_CASE_TIMEOUT_MS);
133133

134134
it('chokidar: own writes do not re-trigger events (self-write suppression)', async () => {

‎packages/metadata-fs/test/no-root-on-attach.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,6 @@ describe('FileSystemRepository — attaching creates nothing on disk (#7000)', (
196196
expect(collected).toHaveLength(1);
197197
expect(collected[0]!.op).toBe('update');
198198
expect(collected[0]!.source).toBe('fs');
199-
expect(collected[0]!.actor).toBe('fs');
199+
expect(collected[0]!.actor).toBeNull();
200200
}, WATCHER_CASE_TIMEOUT_MS);
201201
});

‎packages/metadata-fs/test/self-write-suppression.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ describe('#7335 self-write suppression is keyed on observed content, not on a cl
170170
expect(added).toHaveLength(1);
171171
expect(added[0]!.op).toBe(expectedOp);
172172
expect(added[0]!.source).toBe('fs');
173-
expect(added[0]!.actor).toBe('fs');
173+
expect(added[0]!.actor).toBeNull();
174174
expect(added[0]!.hash).toBe(hashSpec(externalSpec));
175175
});
176176

@@ -306,7 +306,7 @@ describe('#7856 a self-written spec is never republished as an external edit', (
306306
const added = (await readLog(root)).slice(before.length);
307307
expect(added).toHaveLength(1);
308308
expect(added[0]!.op).toBe('update');
309-
expect(added[0]!.actor).toBe('fs');
309+
expect(added[0]!.actor).toBeNull();
310310
expect(added[0]!.hash).toBe(hashSpec(externalSpec));
311311
});
312312
});

‎packages/metadata-fs/test/watch-dot-root.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ describe('FileSystemRepository watcher — dot-rooted watch root (#7150)', () =>
268268
expect(sink.events[0]!.op).toBe('update');
269269
expect(sink.events[0]!.ref.name).toBe('case_grid');
270270
expect(sink.events[0]!.source).toBe('fs');
271-
expect(sink.events[0]!.actor).toBe('fs');
271+
expect(sink.events[0]!.actor).toBeNull();
272272
}, CASE_TIMEOUT_MS);
273273

274274
it('still ignores dot entries UNDER the root, including its own bookkeeping', async () => {

‎packages/metadata-fs/test/watch-write-registration.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,6 @@ describe('FileSystemRepository watcher — writes register their own path (#7282
203203
const updates = events.filter((e) => e.ref.name === 'fresh' && e.op === 'update');
204204
expect(updates).toHaveLength(1);
205205
expect(updates[0]!.source).toBe('fs');
206-
expect(updates[0]!.actor).toBe('fs');
206+
expect(updates[0]!.actor).toBeNull();
207207
}, CASE_TIMEOUT_MS);
208208
});

0 commit comments

Comments
 (0)