Skip to content

The object write door still runs no field-existence rule for searchableFields or listViews — the same asymmetry #15254 closed one key over #15495

Description

@claude

Found while implementing #15254 (PR #15493); filed as a finding only, not claimed.

Measured on origin/main at f01adfa, and still true after #15254

Driving one object publish through the #4463 runtime authoring gate with THREE dangling references at once:

position CLI verdict runtime publish door, before #15254 after #15254
highlightFields[i] was warning nothing REFUSED (object-field-ref-unknown)
searchableFields[i] error / searchable-field-unknown nothing still nothing
listViews.*.columns[i] error / list-view-field-unknown nothing still nothing

Both of the bottom two are ERROR-tier on os validate / os build / os lint today. They do not reach the runtime publish door because their suite members declare runtimeTypes: [(quote)flow(quote), (quote)view(quote)] and an object publish is write type object — so the members are filtered out before they judge.

Why this is the same defect, not a smaller one

#15254 was filed because a click author could publish something a code author is warned about. That card is fixed for highlightFields. But an object's BUILT-IN list views (objects[].listViews.*) and its searchableFields are authored on the same object body, published through the same door, and are error-tier for code — and the click author still publishes a dangling one green.

Why #15254 did not widen it

The suite records that crossing a member onto another write type is an explicit declaration PLUS that type's own false-positive measurement (#4716 priced exactly five gating rules for the object door), never automatic. #15254 crossed one new member whose resolution target is the object's own field map — a snapshot that is trivially complete. Crossing validateSearchableFields and validateListViewFieldRefs onto object looks safe by the same #9313 reasoning (both resolve only against stack.objects, which the per-write snapshot carries), but it wants its own measurement over a real object corpus before it refuses anyone's publish, and it is a refusal widening on the hottest write door.

Suggested shape

Add object to those two members' runtimeTypes in packages/lint/src/reference-integrity-suite.ts, with the false-positive measurement over the shipped object corpus recorded in the PR, and update the runtimeAuthoringRulesFor((quote)object(quote)) pins in runtime-gate.object-writes.test.ts.


Generated by Claude Code

Activity

  1. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Graded — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

    domain:devx · priority:p3, queued: the same click-author asymmetry PR #15493 closed for highlightFields, one key over (searchableFields, listViews.* — ERROR on the CLI, runtimeTypes [flow, view] on the door). Dispatch owes a false-positive measurement over a real object corpus BEFORE widening runtimeTypes, per the card; serial behind #15493 and #15494 on the reference-integrity suite.


    Generated by Claude Code

  2. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, 2026-09-04T23:50Z — dispatching to an os-dev subagent on branch claude/issue-15495-object-door-field-existence. pm:queue → pm:dispatched, assignee = the shared account (identity is this session ID). Serial condition met: PR #15562 (#15494) landed 23:48Z; hot-file check against the still-queued PR #15571 (#15463): it touches authoring-rules.ts, runtime-gate.test.ts, validate-widget-bindings.* — none of this card's files (reference-integrity-suite.ts, runtime-gate.object-writes.test.ts). Ruling as graded (18:20Z): false-positive measurement over the shipped object corpus BEFORE widening runtimeTypes. Live devs after this claim: 3/5.


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15495,
      "status": "done",
      "branch": "claude/issue-15495-object-door-field-existence",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15637",
      "premise_still_valid": true,
      "summary": "The card's premise held on origin/main: the object write door carries stack.objects (RuntimeStackContext.objects, TYPE_TO_STACK_KEY object -> objects), and both validateSearchableFields and validateListViewFieldRefs resolve ONLY against stack.objects, yet both still declared runtimeTypes ['flow','view'] -- so neither judged an object snapshot, exactly the asymmetry #15254 closed one key over. Measured first, then crossed: both members now declare ['flow','view','object'] in reference-integrity-suite.ts, with the population and precision recorded in each member's comment. The runtimeAuthoringRulesFor('object') roster is UNCHANGED (validateReferenceIntegrity was already at the door since #15254), so the member surface got its own by-name pin in runtime-gate.object-writes.test.ts -- the shape the view twin uses -- plus four door controls, and the two refusals end-to-end through the real publishPackageDrafts in packages/metadata-protocol. validateSortableFields measured equally clean and was deliberately NOT crossed (recorded at the pin with an explicit not.toContain). No file outside packages/lint + the metadata-protocol door test changed.",
      "tests": "CORPUS MEASUREMENT (throwaway harness outside the repo, deleted; git status clean). Door's own shape: RuntimeStackContext, per-write, stack.objects only, evaluated DIFFERENTIALLY as runtime-gate.ts does (context = the 115 siblings, candidate = context + item, findings = candidate minus base). Population: every *.object.ts under packages/ and examples/, imported and collected by shape, deduped by object name -- 116 objects (platform-objects 48, app-showcase 24, plugins 19, services 12, app-crm 6, metadata-core 5, app-todo 1, qa/downstream-contract 1; 1 import error, packages/create-objectstack/src/templates/blank -- a template outside the workspace, reported). Declaration coverage so the zero is not a fact about the corpus: 40 objects declare listViews, 105 list views, 666 list-view field-naming positions, 5 searchableFields entries. RESULT: validateSearchableFields 0 findings / 0 false positives / precision 1.0; validateListViewFieldRefs 0 findings / 0 false positives / precision 1.0; validateSortableFields 0 findings (measured, NOT crossed). Non-vacuity in the same sweep: a synthetic object with one stale searchableFields entry and one stale column is refused -- 'searchable-field-unknown :: objects[116].searchableFields[1] :: error' and 'list-view-field-unknown :: objects[116].listViews.all.columns[1] :: error'. THROUGH THE REAL DOOR (runRuntimeAuthoringRules type 'object' over the same 116, lint dist rebuilt between the two legs): BEFORE 2 total errors / 0 from the two members; AFTER 2 total errors / 0 errors and 0 advisories from the two members -- identical, so the crossing adds no refusal to the shipped corpus. The 2 pre-existing errors are other rules and are the state of main: sys_email object-field-ref-unknown at objects.sys_email.highlightFields[1] ('to' vs the real 'to_addresses'), and dc_account security-owd-unset. VERIFICATION, exit codes captured before any pipe, verdict lines quoted: pnpm --filter '@objectstack/lint^...' build EXIT=0 and '@objectstack/metadata-protocol^...' build EXIT=0 (the first metadata-protocol run without the closure was ERR 'Cannot find package @objectstack/metadata/errors' = NOT MEASURED, re-run after building); pnpm --filter @objectstack/lint test EXIT=0 'Test Files 95 passed (95)' 'Tests 2983 passed (2983)'; pnpm --filter @objectstack/lint typecheck EXIT=0 'check:test-typecheck: OK -- @objectstack/lint's test layer compiles under packages/lint/tsconfig.test.json'; pnpm --filter @objectstack/metadata-protocol test EXIT=0 'Test Files 163 passed, 2 skipped (165)' 'Tests 2387 passed, 10 skipped (2397)'; pnpm --filter @objectstack/metadata-protocol typecheck EXIT=0 (its tsconfig includes src/**/* so the edited test file is in the program); pnpm lint (eslint . --no-inline-config, WHOLE REPO, no narrowing claimed) EXIT=0 in 95s under the lock. GATES: node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack EXIT=0, 53 commands from the 4 changed paths -- 51 EXIT=0; pnpm check:dual-build-cjs-loads EXIT=3 'PREREQUISITE NOT MET -- this gate reads built output' = NOT MEASURED; pnpm check:type-check-debt EXIT=3 'Build the closure first' = NOT MEASURED (both need a whole-monorepo build, CI does it). Also run: pnpm check:pm-dispatch-gates EXIT=0 'dispatch-gates self-test: 1445 cases pass.' (no case edited). Seven further path-matched families take a $RUNNER_TEMP / matrix.shard value and are NOT MEASURED outside CI, as the derivation itself names them. The union was run at b8d8c7c18 (git rev-parse --short HEAD), the branch HEAD the PR is opened on; the tree has not moved since. Merged origin/main (f5cc78b63) into the branch before deriving, because the first derivation warned STALE TREE and named scripts/pm/dispatch-gates.mjs itself among the stale files; the post-merge derivation carries no warning and the change set is exactly the 4 paths.",
      "mcp_calls": "1 — one targeted mcp__github__search_issues for the out-of-scope dedupe (REST /search/* is refused by the egress proxy by design; the repo-scoped REST probe returned 200, so every other read and write went over REST). The search returned a known-relevant hit (#14372, a sys_email card) so the empty duplicate result is a reading, not a dead channel.",
      "open_questions": [
        {
          "question": "validateSortableFields is the third sibling on the identical rungs and measured equally clean (0 findings over the same 116-object corpus), but was not crossed. Leaving it out means an object's built-in list view now gates its `columns` while that same list view's `sort` still does not — a new asymmetry inside one list view, of the same shape this card closed.",
          "options": [
            "A — cross it in a follow-up card, with its own non-vacuity control (the probe used here did not exercise a stale sort, so its 0 is measured-but-uncontrolled) and its own door test",
            "B — cross it here, extending this PR's scope past the two axes the card's title names",
            "C — leave it CLI-only and record the asymmetry as deliberate"
          ],
          "recommendation": "A. A crossing is an adjudicated act with its own measurement and its own pin; this card was scoped to two axes, and the member-surface pin in runtime-gate.object-writes.test.ts already asserts `not.toContain('validateSortableFields')`, so the decision is recorded where the third crossing would have to be argued rather than left to drift. B would ship a crossing whose non-vacuity leg was never run."
        }
      ],
      "out_of_scope_findings": [
        "filed as #15629: sys_email.highlightFields[1] names `to` but the field is `to_addresses` — a true dangling reference on a shipped platform object (packages/platform-objects/src/audit/sys-email.object.ts:32), refused at the object door TODAY by #15254's rule, so the object cannot be republished through PUT /api/v1/meta/object. One-line content fix in another package; not ridden along here.",
        "recorded inside #15629 (not filed separately, plausibly intentional): dc_account (packages/qa/downstream-contract/src/account.object.ts) declares no sharingModel, so the same door raises security-owd-unset at error over the shipped corpus."
      ]
    }

    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    LANDED — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

    PR #15637 merged 2026-09-05T01:50:26Z as b4b37e599 on main (squash, via the merge queue). Probed on re-fetched origin/main, textually — packages/lint imports typescript, absent in this seat's checkout, so the suite is NOT MEASURED here; CI's merge group was the arbiter:

    packages/lint/src/reference-integrity-suite.ts:209-215   the searchableFields rung now walks objects[].searchableFields and objects[].listViews.*.searchableFields on the object door
    .changeset/object-door-searchable-listview-refusal.md    "@objectstack/lint": minor / "@objectstack/metadata-protocol": minor
    

    Four files landed: the suite, its runtime-gate object-writes test, the metadata-protocol publish-drafts test, and the changeset.

    Stripping pm:dispatched + assignee; the card closed via Fixes on merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions