Repository navigation
metadata-protocol: findReferencesToMeta ("Used by") misses a permission set that names an object only in its fields keys (OBJECT.FIELD), so the panel can call an object safe to delete while field rules name it #23539
Description
Filing gate ①, finding class (b): a declared contract the producer breaks, measured on a real kernel. Filed by domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy, from #23527's dev report (PR #23536, out-of-scope finding 4; the dispatch asked for this measurement). ⛔ Not a claim. Grade and lane are triage's.
Seam: spec:PermissionSetSchema.fields (keys <entity_name>.<field_name>) → runtime:findReferencesToMeta (REFERENCE_SITES) | consumer: the admin UI's "Used by" panel
reach: protocol.findReferencesToMeta({ type: 'object', name: 'dupsrc_customer' }) on a real kernel (ObjectKernel + ObjectQLPlugin, memory driver) at PR #23536's head ee862ff9d. This is the method GET /api/v1/meta/object/:name/references serves. The HTTP door was not driven.
Reader who acts: triage, to grade and route. The fix is in the derived reference-site index (packages/metadata-protocol/src/reference-sites.ts): it has no arm for a composite OBJECT.FIELD key that names an object. PR #23536 (#23527) fenced that file off on purpose, because widening the index changes this answer.
What happens (measured)
Two permission sets are saved (success: true):
dupsrc_objects, withobjects: { dupsrc_customer: … };dupsrc_only_fls, withobjects: {}andfields: { 'dupsrc_customer.full_name': { readable: false, editable: false } }.
findReferencesToMeta for object dupsrc_customer lists permission/dupsrc_objects@objects{key} but not dupsrc_only_fls. The index reads permission.fields as a site naming field items (its property spells that type), never an object, and the key form is composite.
The contract
reference-sites.ts's own header: "an incomplete table is not a cosmetic gap; it is a green light for a destructive action (ADR-0110 D3, the #8896 harm shape)". The panel's empty state reads "Nothing in the metadata graph points at this item. Safe to delete." PermissionSetSchema.fields documents its keys as <entity_name>.<field_name> (packages/spec/src/security/permission.zod.ts).
What done looks like
findReferencesToMetafor an object lists a permission set whosefieldskeys name that object, with a site path that says it came from afieldskey.- A set whose
fieldskeys name only other objects is not listed for it. - Pin it on a real kernel. Also check that the
fieldtarget answer (finding: thefieldmetadata type can never match as a reference TARGET — items are keyed<object>.<field>while every derived reference site holds a bare field name #9538's ground) does not move unless deliberately.
Dedupe
MCP search_issues on objectstack-ai/objectstack, closed included: findReferencesToMeta Used by misses permission set fields keys OBJECT.FIELD reference sites composite key field-level security → 7 hits:
- metadata-protocol: duplicatePackage leaves a copied permission set's
fieldskeys (OBJECT.FIELD) naming the SOURCE object, so the duplicate's field-level security applies to the source #23527 (open): the duplicate'sfieldskeys, not the references answer. - finding: the
fieldmetadata type can never match as a reference TARGET — items are keyed<object>.<field>while every derived reference site holds a bare field name #9538 (closed as duplicate): thefieldmetadata type can never match as a reference TARGET, because items are keyed<object>.<field>. That is the field target; this card is the object target, read from a composite key. findReferencesToMetaanswers{references: []}for every target type absent fromREFERENCE_PATHS— the "Used by" panel is silent by construction for 20+ declared types #9190, [finding] The REST references route answers a MISSINGfindReferencesToMetacapability with{references: []}— "nothing depends on this item", one layer above the defect #9190 just closed #9326 (closed): the derived index and the route's missing-capability answer.- metadata forms: one row each for object.access, object.highlightFields, object.requiredPermissions, object.searchableFields and permission.adminScope (#19332 flight G1a) #20349, content/docs/ui/views.mdx states a lookup in a view's searchableFields is always refused — measured, it is accepted whenever the object declares it #6897, data: a lookup accepts an id that does not exist in the referenced object — including the RBAC permission-set link tables #4441 (closed): unrelated.
None of them covers this.
Dedupe words: Used by misses permission fields keys; findReferencesToMeta FLS-only permission set; reference sites composite OBJECT.FIELD key names object.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
bug·priority:p2·domain:spec·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T20:55ZRead fresh.
findReferencesToMetafor an object misses a permission set that names it only infieldskeys (OBJECT.FIELD). The derived site index readspermission.fieldsasfield-type sites only. This is measured on a real kernel.priority:p2: this is the index's own declared harm shape.reference-sites.ts's header says an incomplete table is "a green light for a destructive action" (ADR-0110 D3). The "Used by" panel can say "Safe to delete" while field rules name the object.domain:spec(packages/metadata-protocol/src/reference-sites.ts).fieldtarget answer (finding: thefieldmetadata type can never match as a reference TARGET — items are keyed<object>.<field>while every derived reference site holds a bare field name #9538's ground) does not move.findingleaves on grading.objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 3 · 2026-10-14T21:17Z
Session:
session_0121ooBYRmS8ziFXm9ai5YHmAccount:
sunhaolin(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-23539-references-permission-fields-keysWorktree:
objectstack-issue-23539Domain:
domain:specSeat:
domain:spec#4(seat post #18917)File surface (paths on
origin/main7b64a3d73dor later):packages/metadata-protocol/src/reference-sites.ts: one site arm, so that a permission set's compositefieldskey (OBJECT.FIELD) names the object it starts with;packages/metadata-protocol/src/reference-sites.derivation.test.ts, only if the derived table's expected rows move;packages/objectql/src/protocol-references.test.ts;.changeset/23539-*.md.⛔ Not
packages/metadata-protocol/src/protocol.ts'sduplicatePackageregion, which is seat 1's metadata-protocol: duplicatePackage silently merges a copied permission set'sobjectskeys when a renamed key meets one the set already holds: one grant is dropped, and the copy can gain edit and delete the source never granted #23537 in flight. ⛔ Notpackages/spec/**. Stop on a breach and explain it in the report.Container & model:
M,mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate).Clause-②: no
Responsibility: the derived reference-site index in
packages/metadata-protocol/src/reference-sites.tsreadspermission.fieldsonly asfield-type sites, sofindReferencesToMetafor an object misses a set that names the object only in itsfieldskeys | none: no other answer covers the "Used by" panel's object question | an admin deleting an object from the "Used by" panel, which can read "Safe to delete" while field rules name it; measured on a real kernel by the filerThread-read: 6178283554
Ruling-ref: none. Triage
6178283554sets the scope: the card's three bullets, including the check that thefieldtarget answer (finding: thefieldmetadata type can never match as a reference TARGET — items are keyed<object>.<field>while every derived reference site holds a bare field name #9538's ground) does not move. The order is after PR fix(metadata-protocol): duplicatePackage points a copied permission set's field-level rules at the copied objects #23536, which merged as7b64a3d73d.Serial constraints cleared:
reference-sites.ts.objectskeys when a renamed key meets one the set already holds: one grant is dropped, and the copy can gain edit and delete the source never granted #23537 (seat 1) namesprotocol.ts'sduplicatePackageregion and excludesreference-sites.ts.objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 23539,
"status": "blocked",
"branch": "claude/issue-23539-references-permission-fields-keys",
"pr": null,
"session": "session_0121ooBYRmS8ziFXm9ai5YHm",
"premise_still_valid": true,
"summary": "Stopped on a file-surface breach, as the claim instructs. No fix code was written and no PR was opened. The card's defect reproduces on a real kernel at origin/main 7b64a3d: a permission set whose only rule for an object is a
fieldskey is missing from that object's findReferencesToMeta answer. Zone 2's first assumption ('the miss is in the index data, not in the walk') is FALSIFIED, measured with experiment E1 on a real kernel. One plain arm { fromType: 'permission', property: 'fields', target: 'object' } in reference-sites.ts (the claimed file) was built into metadata-protocol dist/ (preflight: marker in dist/index.js and dist/index.cjs). Result (a): the pin stays red with the same 2 failures, because the walk (protocol.ts collectReferenceHits/referencedNamesAt, ~L5296-5375) compares the WHOLE record key 'refsrc_customer.full_name' to the bare object name. A site arm alone cannot match. Result (b): duplicatePackage regresses. Its objectReferenceProperties (protocol.ts L26553-26555, inside the fenced duplicatePackage region L26331-26878) is a flat property-name set over REFERENCE_SITES.byTarget.get('object'), so 'fields' becomes an object-reference property for EVERY item. protocol-duplicate-package-unprefixed-object.test.ts 'references follow the new names; a field calledticketand the prose do not' went RED: noteCopy.fields.ticket is undefined because a copied object's own field key was renamed. So the honest fix needs two things. (1) A seed in reference-sites.ts kept OUT of byTarget, so duplicatePackage's reading is byte-unchanged. (2) A walk change in protocol.ts's findReferencesToMeta + referencedNamesAt/collectReferenceHits regions, which match a seeded record key's text before its first '.' (dot index > 0) against the object name and report path 'fields{key}'. Both protocol.ts regions are disjoint from duplicatePackage, but protocol.ts is not on the claim's file surface. The honest fix does NOT need packages/spec, so this is not needs_decision. The module already has the allowed exception shape: COMPOSITE_KEYED_TARGET_TYPES is a seed 'small enough to argue' with a derived premise guard (nameVocabularyRejectsKey). SEMANTIC_REFERENCE_SITES is the wrong home: its header says it must not grow, and E1 shows the byTarget placement is harmful. The pin is pushed (9e67e00) as the resume point.","blocked_on": "PM: amend the claim's file surface to add packages/metadata-protocol/src/protocol.ts, regions referencedNamesAt + collectReferenceHits (L5296-5375 at 7b64a3d) and findReferencesToMeta (L30082-30325). Both are disjoint from duplicatePackage (L26331-26878, seat 1's #23537). Then re-dispatch with Resume-from: claude/issue-23539-references-permission-fields-keys@9e67e008e1. Without that amendment no in-surface change can list the set, and the one in-surface change that touches byTarget regresses duplicatePackage (measured).",
"proposed_route": "A (recommended): (1) In reference-sites.ts, add a new ReferenceSiteIndex member, e.g. compositeKeyObjectSites = [{ fromType: 'permission', property: 'fields', separator: '.' }], seeded with one member and NOT placed in byTarget. Give it a derived premise guard in the nameVocabularyRejectsKey style: the seed drops out unless (i) the source schema still declares
fieldsas a name-keyed record and (ii) ObjectSchema.name still rejects the separator. (2) In findReferencesToMeta, for target 'object' only, also read the seeded property's record keys and match key.slice(0, key.indexOf('.')) when indexOf > 0. Report path 'fields{key}' and kind 'permission fields'. (3) Derivation pin: add one assertion on the seed and one asserting byTarget('object') still has NO permission.fields site; that second assertion is the duplicatePackage guard. Expected invariants: (i) the field-target 501 for 'refsrc_customer.full_name' is unchanged, because byTarget('field') and unanswerableTargetTypes are untouched; (ii) duplicatePackage's objectReferenceProperties is unchanged. Rejected alternatives: B, put the arm in byTarget with a key-form discriminant and filter it inside duplicatePackage, which edits seat 1's fenced region and couples two consumers to one flag; C, a packages/spec key regex or .meta annotation on PermissionSetSchema.fields, which is the durable producer-side close the module header names, but is a contract narrowing outside this card. The four axes: business need, measured: the save door stores a fields-only set (filer, and this pin's beforeEach asserts success:true for objects:{} + fields). But examples/app-showcase at 7b64a3d has 0 fields-only sets; both sets with field rules (showcase_contributor, showcase_client_liaison) also grant showcase_project inobjects, so they are already listed via objects{key}. Long-term fit: A matches the module's own seed-plus-derived-guard pattern, and C is the eventual producer close. AI-safety: A adds no consumer leniency, and its split mirrors PermissionEvaluator.getFieldPermissions exactly (key.startsWith(object + '.')). Startup scope: A is the smallest change, and no gate is added.","mechanism_measurements": [
"Zone 2 #1 (index data only): FALSIFIED by E1, see summary. Both parts are needed: the index seed and the protocol.ts walk.",
"Zone 2 #2 (allowed exception shape): reference-sites.ts has two hand arms. SEMANTIC_REFERENCE_SITES (L179-181) says it must not grow, and its pin says growing it is a conscious act. COMPOSITE_KEYED_TARGET_TYPES (L240-245) is a seed with a derived premise guard (nameVocabularyRejectsKey, L264-286). The derivation cannot learn the composite key from the schema: PermissionSetSchema.fields is z.record(z.string(), FieldPermissionSchema) (permission.zod.ts:888), and its key form lives only in the TSDoc line 'Field Permissions Map: entity_name.field_name' (L887). Its JSON schema is indistinguishable from object.fields, a record keyed by the BARE field name. So: a seed with a derived guard, or a spec annotation (option C).",
"Zone 2 #3 (split rule): ObjectSchema.name is /^[a-z_][a-z0-9_]$/ (object.zod.ts:2042), and FieldSchema.name has the same pattern (field.zod.ts:1143), so neither can contain '.'. The text before the FIRST '.' is exactly PermissionEvaluator.getFieldPermissions' attribution (key.startsWith(objectName + '.'), permission-evaluator.ts:440). It is also lint security-fls-unknown-field's split (validate-security-posture.ts:817) and duplicatePackage rewriteFieldRuleKeys' split. Save-door probe, real kernel, BASE source, saveMetaItem: 'refsrc_customer.full_name.extra' gets 422 INVALID_METADATA [security-fls-unknown-field]; 'refsrc_customer' (bare) gets 422 [security-fls-unqualified-key]; 'refsrc_customer.' gets 422 [security-fls-unknown-field]; '.full_name' is STORED (success:true); 'no_such_object.x' is STORED (success:true, an out-of-stack object). A deeper key is therefore refused for an in-stack object but storable for an out-of-stack one, and it names the object before its first dot either way. The walk must skip a key with no dot or a dot at index 0; neither names an object."
],
"tests": "HEAD 9e67e00 (only change vs BASE 7b64a3d: the new pin). (1) Build: os-verify-lock -c "pnpm --workspace-concurrency=2 --filter '@objectstack/objectql^...' build" gave VERDICT command-exit 0. (2) Pin on BASE source: pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/protocol-references-permission-field-keys.test.ts gave VERDICT command-exit 1, 'Tests 2 failed | 2 passed (4)'. Failure 1: 'AssertionError: expected [ Array(1) ] to include refsrc_customer_fls@fields{key}'. Failure 2: 'expected [] to deeply equal [ refsrc_invoice_fls@fields{key} ]'. The negative control and the objects{key} row passed, and so did the field-target control (code NOT_IMPLEMENTED, status 501). (3) E1, one plain arm in reference-sites.ts (ablation-replace.mjs WRAP mode, anchor 1->0, blob ca4ca04ff072->8a48d6be80de; on-disk arm count 1; metadata-protocol build exit 0; ablation-dist-preflight marker present in dist/index.js and dist/index.cjs, exit 0). objectql vitest on pin + protocol-duplicate-package-unprefixed-object + protocol-duplicate-package-permission-field-keys + protocol-references gave exit 1, 'Tests 3 failed | 16 passed (19)': the pin's same 2 failures plus 'references follow the new names; a field called
ticketand the prose do not' (TypeError: Cannot read properties of undefined (reading 'reference') at noteCopy.fields.ticket). metadata-protocol reference-sites.derivation.test.ts gave 'Tests 22 passed (22)'. Restore leg: ablation-replace restored blob == HEAD ca4ca04ff072 with git diff HEAD empty. The rebuild gave VERDICT command-exit 0. ablation-dist-preflight --absent: 'marker absent from all 24 built files' and 'working tree clean against HEAD', exit 0. Whole-tree git status --porcelain: 0 lines. The first E1 attempt was a refused no-op (the replacement contained the anchor, so the anchor count stayed 1->1 and ablation-replace exited 1 before the child ran; restored blob == HEAD); no reading was taken from it. (4) Save-door probe: a throwaway file (zz-probe-23539.test.ts), deleted after the run, with tree clean; readings are in mechanism_measurements. (5) pnpm --filter @objectstack/objectql typecheck (tsc --noEmit && check:test-typecheck): VERDICT command-exit 0, 'check:test-typecheck: OK — 40 file(s) / 234 error(s) / 65 pinned signature(s) held in test-typecheck-debt.json', so the new pin adds no test-layer error. The first attempt queue-timed out (exit 99, NOT MEASURED) and was retried on the kept slot. Full objectql / metadata-protocol suites: not run this round. Declared narrowing: no source file changed; the only change is the new pin, run above.","gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at HEAD 9e67e00 derived 53 families from 1 changed path. All 53 were run, each exit captured before any pipe: 51 exit 0. pnpm check:dual-build-cjs-loads exit 3 and pnpm check:lean-entry-closure exit 3 are both 'PREREQUISITE NOT MET' (they read repo-wide built output), so NOT MEASURED. Reconcile: dispatch-gates --repo objectstack-ai/objectstack --ran (COMMAND :: exit CODE) gave '53 derived, 51 run, 2 NOT-MEASURED, 0 UNRUN', exit 0. CI check-runs: none, because no PR exists and push CI runs on main only.",
"line_budget": "n/a: no skills/** or line-ratcheted ledger touched",
"deviations": [
"Stopped on a file-surface breach per the claim ('Stop on a breach and explain it in the report'). pr_create was not spent, and no changeset was written because no published surface changed.",
"First E1 run was a refused no-op (anchor contained in replacement; ablation-replace exit 1, restored). It was re-anchored and run once; the first attempt is reported, not counted.",
"The pin's path spelling 'fields{key}' and its kind are this round's design choice, following the existing objects{key} grammar. The resuming round may adjust it with the fix.",
"Harness attribution reminder names a model-bearing Co-Authored-By trailer. The commit carries AGENTS.md's model-free pair instead (Claude-Session + Co-authored-by: Claude)."
],
"files_changed": [
"packages/objectql/src/protocol-references-permission-field-keys.test.ts (new, +219; commit 9e67e00, pushed; red on main by design: 2 failed | 2 passed)"
],
"mcp_calls": "0",
"api_writes": "1 — POST /repos//issues/23539/comments (this os-dev-report), via scripts/pm/with-fleet.sh --via dispatch (fleet-write relay)",
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: protocol.saveMetaItem on a real kernel (ObjectKernel + ObjectQLPlugin, memory driver, origin/main 7b64a3d), the method behind the metadata save route: a permission set with fields { '.full_name': { readable: false, editable: false } } is STORED (success: true). · evidence: an empty object part can never match any object, because ObjectSchema.name is /^[a-z_][a-z0-9_]$/ (object.zod.ts:2042) and so requires a non-empty name. PermissionEvaluator.getFieldPermissions keeps only keys that startsWith(objectName + '.') (permission-evaluator.ts:440), so the declared mask never enforces in any deployment. Lint security-fls-unqualified-key skips it (it contains a dot), and security-fls-unknown-field leaves it to 'skip 1' as if it named another package's object (validate-security-posture.ts:759 bare-key skip, :821 skip 1, :847 the '.description' disposition). That skip is right for 'no_such_object.x' (also stored, legitimately) but not for an empty object part, which no package can declare. Seam: spec:PermissionSetSchema.fields keys (entity_name.field_name) -> runtime:PermissionEvaluator.getFieldPermissions | consumer: plugin-security FLS. · dedupe words: FLS key empty object part; fields key leading dot accepted; security-fls-unknown-field skip empty object",
"carrier: none (承接者:无) · noted, not filed: the field-target refusal's text says 'every metadata property that names a field holds the BARE field name'. permission.fields is the one derived field site whose keys are the composite OBJECT.FIELD form (permission.zod.ts:887), so that sentence is inaccurate for one site. The refusal itself stays honest, because every other field site holds bare names and the answer would be incomplete. The card's ruling keeps the field target unmoved, so this is recorded only.",
"carrier: #23539 (its resuming round) · noted, not filed: duplicatePackage's objectReferenceProperties reads byTarget('object') as a flat, source-type-blind property-name set. Any object-target site whose property name collides with a non-reference property elsewhere corrupts copies. E1 measured exactly this for 'fields'. Dormant on main, because no such collision exists today. The resume must keep the new seed out of byTarget, and the proposed derivation-pin assertion guards that."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Claim amendment to
6178702290·domain:specseat 4 ·session_0121ooBYRmS8ziFXm9ai5YHm· 2026-10-14T22:24Z · Thread-read: 6179812710The dev stopped on a file-surface breach (report
6179812710). It measured that a site arm inreference-sites.tscannot make the listing work.findReferencesToMeta's walk compares the whole record key with the object name. An arm placed inbyTarget('object')also regressesduplicatePackage: in experiment E1, a copied object's ownfieldskey got renamed. The seat takes the dev's route A and widens the claim.packages/metadata-protocol/src/protocol.ts, two regions only:referencedNamesAtandcollectReferenceHits(about L5309–5375 at7b64a3d73d);findReferencesToMeta(from about L30082).⛔ The
duplicatePackageregion stays out. It is seat 1's metadata-protocol: duplicatePackage silently merges a copied permission set'sobjectskeys when a renamed key meets one the set already holds: one grant is dropped, and the copy can gain edit and delete the source never granted #23537, with PR fix(metadata-protocol): duplicatePackage refuses a copied item whose renamed object reference meets a name it already holds #23545 at about L26327–26867.reference-sites.ts, kept OUT ofbyTarget, with a derived premise guard in the existingnameVocabularyRejectsKeystyle;.(dot index > 0), only for targetobjectand only for seeded sites;byTarget('object')still has nopermission.fieldssite. That second assertion guardsduplicatePackage.protocol.tshunks were read.objectskeys when a renamed key meets one the set already holds: one grant is dropped, and the copy can gain edit and delete the source never granted #23537) sits induplicatePackage.deletePackage.Resume-from: claude/issue-23539-references-permission-fields-keys@9e67e008e1. That is the pushed pin, red onmainby design.Clause-② stays
no: the references answer keeps its declared shape and gains a missing row. A contract review is not owed. The seat's own record covers the round.objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 23539,
"status": "done",
"branch": "claude/issue-23539-references-permission-fields-keys",
"pr": "#23554",
"session": "session_0121ooBYRmS8ziFXm9ai5YHm",
"premise_still_valid": true,
"summary": "Route A, as accepted in claim amendment 6179854093. reference-sites.ts gains a composite-key seed, COMPOSITE_KEY_REFERENCE_SITES: permission.fields names an object on '.'. It is exposed as a new index member, compositeKeyByTarget, and kept OUT of byTarget. Its premises are derived and fail closed: permission must still declare
fieldsas a root record, and ObjectSchema.name must still reject '.'. The target is typed 'object' alone. nameVocabularyRejectsKey's schema read is factored into nameVocabularyVerdict, with the same answers. protocol.ts changes only in the two amended regions. A new collectCompositeKeyHeadHits sits after collectReferenceHits; referencedNamesAt and collectReferenceHits are byte-unchanged. It reads only the seeded root record's keys and matches key.slice(0, key.indexOf('.')) when indexOf > 0, one row per site at path 'fields{key}'. findReferencesToMeta folds the composite sites into its one-read-per-source-type matchers, with kind 'permission fields'. duplicatePackage is untouched, and its hunk count is 0. The real-kernel pin went red on main (round 1), is green now, and goes red again when the walk branch is ablated. The field-target 501 is byte-identical before and after. The duplicatePackage and references suites are green. Draft PR #23554 is open: assignee sunhaolin,Fixes #23539,Clause-②: noat line start, changeset @objectstack/metadata-protocol patch.","tests": "Final head 8ca891b = fix commit over a merge of origin/main 0fb55fc (resume BASE 83ccf95). Every run went through os-verify-lock; VERDICT lines are quoted. (1) Pin, packages/objectql/src/protocol-references-permission-field-keys.test.ts: red on main in round 1 (9e67e00 over 7b64a3d), 'Tests 2 failed | 2 passed (4)'. After the fix: 'Tests 4 passed (4)'. (2) Field-target byte-identity: a real-kernel throwaway probe (deleted afterwards), run on the BASE build and again on the fix build, gave 'NOT_IMPLEMENTED 501 true' both times with message sha256 7b4aee8698a564f69599ba38e1b3f54ec2623cd5336a8e57e267b093ea4b2457. refsrc_customer: BASE answered [grant@objects{key}]; the fix answers [refsrc_customer_fls@fields{key} kind 'permission fields', grant@objects{key} unchanged]. refsrc_invoice: BASE answered []; the fix answers [refsrc_invoice_fls@fields{key}], one row for two keys. (3) Invariant suites on the fix build: objectql pin + protocol-references + protocol-duplicate-package-unprefixed-object + protocol-duplicate-package-permission-field-keys gave 'Test Files 5 passed (5)', 'Tests 24 passed (24)' (the five files included the probe). metadata-protocol reference-sites.derivation.test.ts gave 'Tests 25 passed (25)'. VERDICT command-exit 0. (4) Ablation, deleting the walk branch (the collectCompositeKeyHeadHits call line in findReferencesToMeta). Positive control on the pristine build: the call marker is in dist/index.js and dist/index.cjs (grep -c 1 and 1). Mutation via ablation-replace.mjs --delete in WRAP mode: anchor 1->0, blob 5be7b1c1adc7->09028903ed91, on-disk count 0. Build: ESM and CJS emitted; DTS refused with TS6133 (helper now unused), so build-exit was 1. The suite loads the JS. ablation-dist-preflight --absent: 'marker absent from all 22 built files', exit 0. Pin: 'Tests 2 failed | 2 passed (4)', the same two listing assertions. Restore: blob == HEAD 5be7b1c1adc7, git diff HEAD empty. Rebuild: check-dts-emitted 2/2, 24 dist files. Preflight in default mode: 'marker present in 2 built files' and 'working tree clean against HEAD'. Pin: 'Tests 4 passed (4)'. VERDICT command-exit 0. Whole-tree git status --porcelain: 0. (5) Full suites. pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 'Test Files 236 passed | 3 skipped (239)', 'Tests 28412 passed | 19 skipped (28431)', VERDICT command-exit 0. pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: 'Test Files 419 passed (419)', 'Tests 8112 passed (8112)', VERDICT command-exit 0. (6) Typecheck: pnpm --filter @objectstack/metadata-protocol typecheck and pnpm --filter @objectstack/objectql typecheck gave VERDICT command-exit 0, with 'check:test-typecheck: OK — 40 file(s) / 234 error(s) / 65 pinned signature(s) held'. metadata-protocol tsc --listFiles includes reference-sites.ts, protocol.ts and reference-sites.derivation.test.ts.",
"gates": "Local, on final head 8ca891b after git fetch: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 67 commands from 5 paths. All 67 were run, each exit captured before any pipe. 66 exited 0. pnpm check:dual-build-cjs-loads exited 3 with PREREQUISITE NOT MET (about 70 packages unbuilt in this worktree), so it is NOT MEASURED and CI-owned. pnpm check:lean-entry-closure first exited 3 on a missing packages/objectql/dist; after pnpm --filter @objectstack/objectql build it was re-run at exit 0 ('Admitted set held exactly (15 packages)'). Reconcile: dispatch-gates --repo objectstack-ai/objectstack --ran (COMMAND :: exit CODE) gave '67 derived, 66 run, 1 NOT-MEASURED, 0 UNRUN', exit 0. The 11 wide-population and 6 workflow-valued families are CI's. PR #23554 check-runs at report time, on head 8ca891b: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped. in_progress: Lint · docs & code guards, Lint · ESLint, Lint · PM dispatch-gates, Lint · repo & tooling gates, Lint · CI, release & engine gates, Lint · scoped (query-options erasure, slot lookup & corpus, tooling self-tests), Type Check · workspace, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Check Changeset, Check PR Size, Check Documentation Links, Governed Surface Queue Guard, single-writer-path guard, card-claims-branch guard, Part-of guard, Flag docs affected, filter. The CI-only lanes (Test Core, Build Core) had not reported yet.",
"line_budget": "n/a: no skills/** or line-ratcheted ledger touched",
"deviations": [
"The first relay pr_create (run 38652286674) concluded failure: executor exit 5, EXIT_ACTION_FAILED. A list read confirmed zero writes landed: no PR for the head. The reason is unreadable from this container: the job log answers Forbidden, output.summary is null, and annotations only name landed ops. It was retried once on the same route (run 38652457000) and succeeded, opening PR #23554 with the body read back byte-identical (8148/8148) and assignee sunhaolin.",
"Resume merged origin/main (merge commit 83ccf95) per the resume rule. main had touched none of the affected packages.",
"nameVocabularyRejectsKey's body was factored into nameVocabularyVerdict and metadataTypeJsonSchema, inside reference-sites.ts, with the same answers. The existing unanswerableTargetTypes pins (['field'], email_template and capability not refused) still pass.",
"The ablation's mutated build exited 1 at DTS only (TS6133: the helper was unused). ESM and CJS were emitted, the suite reads the JS, and the reading is valid. The restore rebuild re-emitted both .d.ts files (2/2).",
"Labels: zero writes, because the dispatch limits writes to pr_create plus one report comment. A changeset is present, so skip-changeset does not apply.",
"The harness attribution reminder names a model-bearing Co-Authored-By trailer and a PR footer. Commits carry AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The PR body ends in AGENTS.md's session-URL footer block, which the relay's pr_create read-back checks.",
"Cleanup: the worktree and its node_modules are removed after this comment is posted. The branch stays on origin at 8ca891b."
],
"files_changed": [
".changeset/23539-references-permission-fields-keys.md (+11/-0, new; @objectstack/metadata-protocol patch)",
"packages/metadata-protocol/src/reference-sites.ts (+140/-8)",
"packages/metadata-protocol/src/protocol.ts (+73/-4; hunks only after collectReferenceHits at new L5382-5423 and inside findReferencesToMeta at new L30272-30363; duplicatePackage untouched)",
"packages/metadata-protocol/src/reference-sites.derivation.test.ts (+26/-0)",
"packages/objectql/src/protocol-references-permission-field-keys.test.ts (+219/-0, new)"
],
"mcp_calls": "0",
"api_writes": "This round, 3 relay dispatches via scripts/pm/with-fleet.sh --via dispatch: (1) pr_create run 38652286674, failed with zero writes; (2) pr_create run 38652457000, POST /repos/objectstack-ai/objectstack/pulls (PR #23554, draft forced) plus POST /repos//issues/23554/assignees; (3) this os-dev-report, POST /repos//issues/23539/comments. Round 1: 1 comment, 6179812710.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: domain:spec seat 4 (the seat files it, per the resume message) · noted, not filed: class c, save door stores an empty-object-part FLS key. A permission set with fields { '.full_name': ... } is stored (saveMetaItem success:true, real kernel), and no object can ever match it. Evidence and Seam line are in round-1 report 6179812710.",
"carrier: none (承接者:无) · noted, not filed: the field-target refusal text says every property that names a field holds the BARE field name. permission.fields is the one derived field site with composite OBJECT.FIELD keys, so the sentence is inaccurate for one site. The refusal itself stays honest, and the card keeps the field answer unmoved.",
"carrier: #23539 · noted, not filed: duplicatePackage reads byTarget('object') as a source-type-blind property-name set. This PR keeps the seed out of byTarget, and the derivation pin asserts that byTarget('object') has no permission.fields site."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
ACCEPT → PR #23554 comment 6180579736 ·
domain:specseat 4 ·session_0121ooBYRmS8ziFXm9ai5YHm· 2026-10-14T23:15ZRoute A as amended: a composite-key seed kept out of
byTarget, and a separate head-of-key walk insidefindReferencesToMeta.duplicatePackageis untouched. The seat lands the PR once every check on8ca891b1d5is green or skipped by design and the skip roster answers OK.objectstack-fleet commentedon Oct 15, 2026
Landed · PR #23554 ·
domain:specseat 4 ·session_0121ooBYRmS8ziFXm9ai5YHm· 2026-10-15T00:13Z7ffc57846aorigin/main7ffc57846a.collectCompositeKeyHeadHitsis inpackages/metadata-protocol/src/protocol.tsthere, andCOMPOSITE_KEY_REFERENCE_SITESinreference-sites.tsgh-readonly-queue/main/pr-23554-*ref remainsState change: the card is closed
completedbyFixes #23539, andpm:dispatchedis removed in this act.An object's "Used by" answer now lists a permission set that names it only in a
fieldskey.duplicatePackageis unchanged: the composite seed stays out ofbyTarget, and the derivation pin guards that. The finding this round surfaced is filed as #23549, now p1 with seat 1. The field-target refusal's "bare field name" sentence stays an Acceptance note,carrier:none.