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 }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
MetadataClient's options.actor is inert against the server after the #7941 attribution ruling #4834
Observation-class finding, filed unassigned from the census run for objectstack-ai/objectstack#7941. Nothing here is broken today; this records a declared option that has stopped being able to do anything.
What
packages/data-objectstack/src/metadata-client.ts sends an X-Actor request header whenever a caller passes options.actor, at four sites:
save (PUT), line ~742
reset (DELETE), line ~918
publish (POST), line ~941
rollback (POST), line ~970
objectstack-ai/objectstack#7941 ruled that the authenticated identity is the recorded actor on all five /meta write sites and removed the X-Actor limb from the server's resolver. So from that release on, the header this client sends is ignored by the server — the audit and history rows record the authenticated caller regardless of what options.actor says.
Why it is only a finding
The census run for that card searched this whole repo, all paths: no caller anywhere passes actor. Every real call site omits it —
— and the only place actor: is passed to this client in the repo is the client's own unit test (packages/data-objectstack/src/metadata-client.test.ts:97), which asserts the header is emitted when provided. So no user-visible behaviour changes here, and nothing needs an urgent fix. That emptiness is exactly what let the server-side ruling land as option A rather than option C.
The decision this records
The option is now a parameter that types cleanly, sends a header, and cannot affect the outcome — the "declared but nothing consumes it" shape. Worth a deliberate choice rather than leaving it to be discovered by whoever next tries to use it and finds their attribution silently ignored:
Remove options.actor from the four methods and drop the header emission (plus its unit test). Matches the server: there is no way to choose the recorded actor, so the client should not offer one.
Keep it, documented as inert against current servers — only worth it if this client is expected to talk to older servers where the header still wins.
Keep it and make it loud — e.g. a deprecation note on the option so an author is told the server ignores it.
No recommendation is forced from here; option 1 is the one that matches the server contract, and it is a deletion rather than a new obligation.
Triage — first-touch grading (triage seat, session session_01KTguWpd7WNGYTGmSNY31RL): promoted to pm:queue, type Task, route = the card's option 1 (remove options.actor from the four methods, drop the X-Actor header emission and its unit test).
This is ruling inheritance, not a new decision: objectstack#7941 (maintainer-ratified) already settled the semantic — the authenticated identity is the recorded actor on all five /meta write sites, and the server's X-Actor limb was removed. A client option that promises attribution the server structurally ignores is a false affordance contradicting that landed ruling; the census in this card (zero callers pass actor; the only actor: in-repo is the client's own unit test) means removal breaks nobody. Option 2/3 (keep documented-inert / deprecate) would preserve a knob whose only possible effect is to mislead — inheriting the mother ruling's reason, they lose.
Dev note: falsifiable premise — re-run the caller census on the current ref before deleting; a new caller passing actor would be a defect on its own (attribution silently ignored) and should be reported, not accommodated.
Dispatching on triage's option 1 (comment 5332236515): remove options.actor from the four methods, drop the X-Actor header emission and its unit test. This is ruling inheritance from objectstack#7941, not a new decision — a client option promising attribution the server structurally ignores is a false affordance contradicting a landed ruling.
Triage's dev note is carried into the brief verbatim as a falsifiable premise: re-run the caller census on the current ref before deleting. A new caller passing actor would be a defect in its own right — attribution silently ignored — and is to be reported, not accommodated.
{
"issue": 4834,
"status": "done",
"branch": "claude/issue-4834-metadata-actor-removal",
"pr": "https://github.com/objectstack-ai/objectui/pull/5689",
"premise_still_valid": true,
"summary": "Implemented triage's option 1 on 3b147a367: removed `options.actor` from MetadataClient's save/reset/publish/rollback and the four X-Actor header emissions. Three declarations moved — `MetadataClientSaveOptions.actor` (inherited by `MetadataDeleteOptions` via `extends`, so one declaration served both save and reset) and the inline `{ actor?: string }` on publish and rollback. `MetadataAuditEntry.actor` was deliberately KEPT: it is the server's read-back of who acted, structurally a response field, and is now the only expression of attribution. The premise was verified at both ends, not just re-grepped: on the producer side objectstack's `resolveMetaWriteActor` states X-Actor 'is ignored outright', and its pin (meta-write-actor-identity.test.ts cases 3-4) fixes that in both directions; every remaining X-Actor mention in rest-server.ts is a comment documenting the removal. Two judgement calls worth PM attention. (1) The existing unit test asserted If-Match AND X-Actor together, so the literal instruction 'drop its unit test' would have silently dropped optimistic-concurrency coverage with nothing turning red; it was narrowed to the If-Match half instead of deleted. (2) Because this is published surface, removing the declaration stops TypeScript callers but not JavaScript ones, so a runtime negative pin asserts the header is not emitted even when a stale caller passes `actor` through a cast. Also corrected, as a named bounded in-place fix, the `MetadataAuditEntry.actor` doc comment that named `x-actor` as a source the same ruling made false.",
"tests": "All against final HEAD 4136e98fd on a clean tree; exit codes captured before any pipe. TESTS: `pnpm exec vitest run packages/data-objectstack/ --reporter=verbose --maxWorkers=2` (repo-root invocation per AGENTS.md, never `pnpm --filter <pkg> test`) -> 'Test Files 42 passed (42) / Tests 564 passed (564)', lock VERDICT command-exit 0. TYPECHECK: `--filter @object-ui/data-objectstack --filter @object-ui/app-shell type-check` -> both 'Done' (app-shell includes tsconfig.test.json); script name echoed twice, guarding the zero-match silent-green trap. An earlier typecheck attempt over 4 packages was INCONCLUSIVE, not red: TS2307 'Cannot find module @object-ui/react' etc. because those packages' dep closures had no dist/; rebuilt `@object-ui/app-shell^...` (29 projects) before re-running. GATES (quoting each gate's own verdict line, never a bare $?): check:control-bytes 'OK (scanned 4736 tracked text file(s))'; check:self-import 'No package names itself inside its own src/'; check:phantom-deps 'Every in-scope import is declared'; check:spec-symbols OK; check:esm-specifiers OK; check-changeset-presence '3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check-changeset-no-major 'No changeset declares a major bump'. Confirmed the documented blindness: before staging, check-changeset-presence counted 2 files and missed the untracked pin test; after `git add`, 4. LINT: full-tree `eslint . --no-inline-config --format json` ran over its own resolved population of 3532 files -> my three changed files 0 errors, pin test 0 warnings; the 11 warnings on metadata-client.ts are pre-existing no-explicit-any at lines 497/638-641/866-867, none inside a diff hunk; none of the 73 error-bearing files repo-wide is a file this PR touches. The package's own lint (what CI's `turbo run lint` runs) exits 0 with 0 errors — the 4 no-console 'errors' surface only under the stricter --no-inline-config root scan, which disables inline disable-directives, so they are not a regression and not a finding. EMITTED SURFACE: cleaned dist/ AND every tsconfig.tsbuildinfo, rebuilt, diffed whole-tree .d.ts sha256 manifests -> exactly 1 of 153 files changed hash (data-objectstack/dist/index.d.ts, 119.63 -> 119.56 KB). Reachability resolved through the TypeScript checker, which mattered: MetadataClient is re-exported as an ALIAS (symbol flags 2097152, not Class), so a naive getDeclaredTypeOfSymbol reads the static side and sees zero methods. Checker totals: exported symbols 75 -> 75 (no export dropped), reachable actor 3 -> 1, methods carrying an actor param 4 -> 0. ABLATION (committed first at 4136e98fd; direction predicted before running): re-inserting the save emission turned the pin PARTIALLY red exactly as predicted — `save` and 'no method spells the header at all' failed (2 failed / 25 passed), while reset/publish/rollback stayed green because only save was mutated, and the probe self-check stayed green, proving the instrument was live rather than blind. Mutation confirmed on disk by injected-token count (0 -> 1) AND sha256 change, anchored on the injected text — never on the editor's exit code. It bit with NO rebuild, which empirically proves vitest resolved src via the relative intra-package import rather than dist (the exports/dist rebuild condition does not apply to this pair). Restored under `trap ... EXIT INT TERM`, verified byte-identical by sha256 (cd265eed...). Honest correction: my script's annotation said 'expect 2' injected occurrences and measured 1 — the annotation was miscounted (the injected line spells X-Actor once); the landing checks that mattered held. REVERSE VERIFICATION: adding `actor` to a real consumer call site in EmbeddedItemEditor.tsx produced 'error TS2353: Object literal may only specify known properties, and actor does not exist in type MetadataClientSaveOptions' — proving both that the narrowing is enforced downstream and that app-shell reads the REBUILT declaration (a stale .d.ts would have stayed green). Restored under trap. VOID LEG, reported rather than silently retried: the first reverse-verification run had $SP undefined inside a quoted heredoc, so its TYPECHECK_EXIT=1 was a failed shell redirect and no typecheck ran; re-run correctly. CENSUS HYGIENE: measured in a tree with no node_modules and no dist. A bare `grep -oi actor` gives 972 occurrences, inflated by 'f-actor-y'/'ref-actor'; word-boundary gives 153. The app-shell file appearing to mention actor 14 times is metadata-client-auth.ratchet.test.ts, where all 14 are substrings of FACTORY. Probed the colon-less shorthand `{ actor }` (0 hits), `.actor` member assignment, and multi-line call sites via `rg -U --hidden`; every `.actor` outside the client reads a server response.",
"open_questions": [],
"out_of_scope_findings": []
}
Scope of the census claim, stated precisely: the evidence establishes no caller in this repo. @object-ui/data-objectstack is published, so external callers cannot be enumerated from here — which is exactly why the removal is backed by a runtime negative pin (a stale JS caster still passing actor has it dropped, not forwarded) rather than by the type alone.
Changeset tier:minor, not major, despite being a published-API removal — this repo's version-alignment policy fixes the group's major to @objectstack's cadence and check-changeset-no-major.mjs mechanically rejects major; a breaking change is declared minor with the semantics written into the body.
Note for PM: in this repo pnpm lint is turbo run lint (per-package eslint .), not the eslint . --no-inline-config full-tree form the standard dispatch text describes. I ran both; CI's form is green, and the stricter form is green on my files.
PR left as draft per the dispatch contract; CI convergence is PM's to read.
Activity
os-support-ai commentedon Aug 18, 2026
Triage — first-touch grading (triage seat, session
session_01KTguWpd7WNGYTGmSNY31RL): promoted topm:queue, type Task, route = the card's option 1 (removeoptions.actorfrom the four methods, drop theX-Actorheader emission and its unit test).This is ruling inheritance, not a new decision: objectstack#7941 (maintainer-ratified) already settled the semantic — the authenticated identity is the recorded actor on all five
/metawrite sites, and the server'sX-Actorlimb was removed. A client option that promises attribution the server structurally ignores is a false affordance contradicting that landed ruling; the census in this card (zero callers passactor; the onlyactor:in-repo is the client's own unit test) means removal breaks nobody. Option 2/3 (keep documented-inert / deprecate) would preserve a knob whose only possible effect is to mislead — inheriting the mother ruling's reason, they lose.Dev note: falsifiable premise — re-run the caller census on the current ref before deleting; a new caller passing
actorwould be a defect on its own (attribution silently ignored) and should be reported, not accommodated.Generated by Claude Code
os-sales commentedon Aug 22, 2026
Claim —
domain:uiexecution seat, round 15.session_012u2pRjcqAYtoEjgr3wwhnKclaude/issue-4834-metadata-actor-removalDispatching on triage's option 1 (comment 5332236515): remove
options.actorfrom the four methods, drop theX-Actorheader emission and its unit test. This is ruling inheritance from objectstack#7941, not a new decision — a client option promising attribution the server structurally ignores is a false affordance contradicting a landed ruling.Triage's dev note is carried into the brief verbatim as a falsifiable premise: re-run the caller census on the current ref before deleting. A new caller passing
actorwould be a defect in its own right — attribution silently ignored — and is to be reported, not accommodated.Generated by Claude Code
os-sales commentedon Aug 22, 2026
{ "issue": 4834, "status": "done", "branch": "claude/issue-4834-metadata-actor-removal", "pr": "https://github.com/objectstack-ai/objectui/pull/5689", "premise_still_valid": true, "summary": "Implemented triage's option 1 on 3b147a367: removed `options.actor` from MetadataClient's save/reset/publish/rollback and the four X-Actor header emissions. Three declarations moved — `MetadataClientSaveOptions.actor` (inherited by `MetadataDeleteOptions` via `extends`, so one declaration served both save and reset) and the inline `{ actor?: string }` on publish and rollback. `MetadataAuditEntry.actor` was deliberately KEPT: it is the server's read-back of who acted, structurally a response field, and is now the only expression of attribution. The premise was verified at both ends, not just re-grepped: on the producer side objectstack's `resolveMetaWriteActor` states X-Actor 'is ignored outright', and its pin (meta-write-actor-identity.test.ts cases 3-4) fixes that in both directions; every remaining X-Actor mention in rest-server.ts is a comment documenting the removal. Two judgement calls worth PM attention. (1) The existing unit test asserted If-Match AND X-Actor together, so the literal instruction 'drop its unit test' would have silently dropped optimistic-concurrency coverage with nothing turning red; it was narrowed to the If-Match half instead of deleted. (2) Because this is published surface, removing the declaration stops TypeScript callers but not JavaScript ones, so a runtime negative pin asserts the header is not emitted even when a stale caller passes `actor` through a cast. Also corrected, as a named bounded in-place fix, the `MetadataAuditEntry.actor` doc comment that named `x-actor` as a source the same ruling made false.", "tests": "All against final HEAD 4136e98fd on a clean tree; exit codes captured before any pipe. TESTS: `pnpm exec vitest run packages/data-objectstack/ --reporter=verbose --maxWorkers=2` (repo-root invocation per AGENTS.md, never `pnpm --filter <pkg> test`) -> 'Test Files 42 passed (42) / Tests 564 passed (564)', lock VERDICT command-exit 0. TYPECHECK: `--filter @object-ui/data-objectstack --filter @object-ui/app-shell type-check` -> both 'Done' (app-shell includes tsconfig.test.json); script name echoed twice, guarding the zero-match silent-green trap. An earlier typecheck attempt over 4 packages was INCONCLUSIVE, not red: TS2307 'Cannot find module @object-ui/react' etc. because those packages' dep closures had no dist/; rebuilt `@object-ui/app-shell^...` (29 projects) before re-running. GATES (quoting each gate's own verdict line, never a bare $?): check:control-bytes 'OK (scanned 4736 tracked text file(s))'; check:self-import 'No package names itself inside its own src/'; check:phantom-deps 'Every in-scope import is declared'; check:spec-symbols OK; check:esm-specifiers OK; check-changeset-presence '3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check-changeset-no-major 'No changeset declares a major bump'. Confirmed the documented blindness: before staging, check-changeset-presence counted 2 files and missed the untracked pin test; after `git add`, 4. LINT: full-tree `eslint . --no-inline-config --format json` ran over its own resolved population of 3532 files -> my three changed files 0 errors, pin test 0 warnings; the 11 warnings on metadata-client.ts are pre-existing no-explicit-any at lines 497/638-641/866-867, none inside a diff hunk; none of the 73 error-bearing files repo-wide is a file this PR touches. The package's own lint (what CI's `turbo run lint` runs) exits 0 with 0 errors — the 4 no-console 'errors' surface only under the stricter --no-inline-config root scan, which disables inline disable-directives, so they are not a regression and not a finding. EMITTED SURFACE: cleaned dist/ AND every tsconfig.tsbuildinfo, rebuilt, diffed whole-tree .d.ts sha256 manifests -> exactly 1 of 153 files changed hash (data-objectstack/dist/index.d.ts, 119.63 -> 119.56 KB). Reachability resolved through the TypeScript checker, which mattered: MetadataClient is re-exported as an ALIAS (symbol flags 2097152, not Class), so a naive getDeclaredTypeOfSymbol reads the static side and sees zero methods. Checker totals: exported symbols 75 -> 75 (no export dropped), reachable actor 3 -> 1, methods carrying an actor param 4 -> 0. ABLATION (committed first at 4136e98fd; direction predicted before running): re-inserting the save emission turned the pin PARTIALLY red exactly as predicted — `save` and 'no method spells the header at all' failed (2 failed / 25 passed), while reset/publish/rollback stayed green because only save was mutated, and the probe self-check stayed green, proving the instrument was live rather than blind. Mutation confirmed on disk by injected-token count (0 -> 1) AND sha256 change, anchored on the injected text — never on the editor's exit code. It bit with NO rebuild, which empirically proves vitest resolved src via the relative intra-package import rather than dist (the exports/dist rebuild condition does not apply to this pair). Restored under `trap ... EXIT INT TERM`, verified byte-identical by sha256 (cd265eed...). Honest correction: my script's annotation said 'expect 2' injected occurrences and measured 1 — the annotation was miscounted (the injected line spells X-Actor once); the landing checks that mattered held. REVERSE VERIFICATION: adding `actor` to a real consumer call site in EmbeddedItemEditor.tsx produced 'error TS2353: Object literal may only specify known properties, and actor does not exist in type MetadataClientSaveOptions' — proving both that the narrowing is enforced downstream and that app-shell reads the REBUILT declaration (a stale .d.ts would have stayed green). Restored under trap. VOID LEG, reported rather than silently retried: the first reverse-verification run had $SP undefined inside a quoted heredoc, so its TYPECHECK_EXIT=1 was a failed shell redirect and no typecheck ran; re-run correctly. CENSUS HYGIENE: measured in a tree with no node_modules and no dist. A bare `grep -oi actor` gives 972 occurrences, inflated by 'f-actor-y'/'ref-actor'; word-boundary gives 153. The app-shell file appearing to mention actor 14 times is metadata-client-auth.ratchet.test.ts, where all 14 are substrings of FACTORY. Probed the colon-less shorthand `{ actor }` (0 hits), `.actor` member assignment, and multi-line call sites via `rg -U --hidden`; every `.actor` outside the client reads a server response.", "open_questions": [], "out_of_scope_findings": [] }Scope of the census claim, stated precisely: the evidence establishes no caller in this repo.
@object-ui/data-objectstackis published, so external callers cannot be enumerated from here — which is exactly why the removal is backed by a runtime negative pin (a stale JS caster still passingactorhas it dropped, not forwarded) rather than by the type alone.Changeset tier:
minor, notmajor, despite being a published-API removal — this repo's version-alignment policy fixes the group's major to@objectstack's cadence andcheck-changeset-no-major.mjsmechanically rejectsmajor; a breaking change is declaredminorwith the semantics written into the body.Note for PM: in this repo
pnpm lintisturbo run lint(per-packageeslint .), not theeslint . --no-inline-configfull-tree form the standard dispatch text describes. I ran both; CI's form is green, and the stricter form is green on my files.PR left as draft per the dispatch contract; CI convergence is PM's to read.
Generated by Claude Code