Skip to content

lint: twelve field-path rules and two private readers judge object fields without the objectExtensions fold, so a view, report, dataset or page naming an extension-added field is refused #30383

Closed

Description

@objectstack-fleet

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Filing gate: ③ the carried census of #30303 (PR #30359), carried as a follow-up so that PR stays at the one rule the card named. ⛔ Not a claim. Suggested-grade: p2, because the same multi-package artifact that hit #30303 (HotCRM, hotcrm#2315: a module package adds fields to an app package's object through objectExtensions) will hit these rules as soon as a view, report, dataset, page or widget names one of those fields. Route: domain:spec (packages/lint).

What an author sees. The runtime serves an object with every body's objectExtensions folded in. These lint rules index the declared objects only, so a field path that names an extension-added field reads as unknown and is refused (or mis-judged), although the served object has the field.

Why. PR #30359 adds one shared fold in packages/lint/src/object-graph.ts (objectExtensionsByTarget, objectsAsServed) that mirrors the registry's merge. The FLS-key check, the controlled-by-parent merge (#23210) and the membership-key check read it. These still read indexObjectGraph over declared objects only (census at PR #30359's head, by reading, not reproduced per rule):

  • validate-dataset-measure-aggregates.ts
  • validate-dataset-references.ts
  • validate-field-consumers.ts (its header says extensions are not judged)
  • validate-list-view-field-refs.ts
  • validate-object-field-refs.ts
  • validate-page-field-bindings.ts
  • validate-preset-comparands.ts
  • validate-report-runtime-filter.ts
  • validate-rls-predicate-enforceability.ts
  • validate-sharing-rule-enforceability.ts (indexes the declared anchor only)
  • validate-thank-you-record-fields.ts
  • validate-widget-bindings.ts

Two more read extension fields through private readers of their own:

  • validate-related-list-refs.ts: the stack's top-level objectExtensions only, with no packages[] bodies;
  • validate-mapping-target-fields.ts (extensionFieldsByTarget): the top level plus every body, additive, without the registry's one-entry-per-package replacement.

Wanted

  • Move each rule above onto objectsAsServed (or the shared objectExtensionsByTarget), one fold for all. For each one, say whether an extension-added field should satisfy it. A rule that judges a security posture on the declared anchor (sharing, RLS) may deliberately stay on declared fields; if so, say why in its header.
  • Retire the two private readers onto the shared fold.
  • A pin per moved rule: an extension-added field passes; a field no body declares or extends is still refused.

Blocked-by: #30303

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Nov 5, 2026
  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 kept · domain:spec kept · 2026-11-05T09:06Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Claim: PM loop round 19 (p2)
    Session: session_01WGufzfgvGexKn9vLHsy89K
    Account: os-support-ai
    Branch: the card thread's own claude/project-thread-* branch, named in the dev's hand-back and restated in a later claim (a measured platform exception to claude/issue-30383-…)
    Worktree: objectstack-issue-30383
    Domain: domain:spec
    Seat: domain:spec#3
    File surface: the twelve packages/lint/src/validate-*.ts rules and the two private readers the card lists, packages/lint/src/object-graph.ts (the shared fold landed by #30359), and their tests. ⛔ Not the runtime registry merge, not HotCRM.
    Container & model: M, mode:thread, model: TIER_DEFAULT, effort: high
    Clause-②: yes (widening: field-path rules accept fields a sibling package adds through objectExtensions, which the runtime already serves; any rule that deliberately stays on declared fields says why in its header)
    Metadata-protocol: none (lint judgements, no authorable key; the dev confirms with check-changeset-no-major)
    Responsibility: view, report, dataset, page and widget field paths on an extension-added field are refused or misjudged by lint while the runtime serves the field | twelve rules index declared objects only and two private readers fold extensions differently from the registry | every multi-package artifact using objectExtensions; HotCRM (hotcrm#2315) is next to hit it
    Thread-read: 6654065461
    Premise: holds by the census at PR #30359's head (8aeb3212); the shared fold objectsAsServed / objectExtensionsByTarget is on origin/main at ad0fbfd1. The dev re-measures each rule before editing.
    Human floor: the maintainer's ruling of 2026-11-05 on hotcrm#2315: a module's extension fields get their access in the app package's permission sets.
    Serial constraints: open draft PR #30270 (#30093, another lane) touches validate-page-field-bindings.ts. The dev moves that rule last and rebases onto it if it lands first; ⛔ no edits on #30270's branch.

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Claim: PM loop round 19 (p2), restated with the branch
    Session: session_01WGufzfgvGexKn9vLHsy89K
    Account: os-support-ai
    Branch: claude/project-thread-tbzgwm (the card thread's own branch, a measured platform exception to claude/issue-30383-…); card thread session session_01WGufzg3UmwhpAtL969c7YG
    Worktree: objectstack-issue-30383
    Domain: domain:spec
    Seat: domain:spec#3
    File surface: the twelve packages/lint/src/validate-*.ts rules and the two private readers the card lists, packages/lint/src/object-graph.ts (the shared fold landed by #30359), and their tests. ⛔ Not the runtime registry merge, not HotCRM.
    Container & model: M, mode:thread, model: TIER_DEFAULT, effort: high
    Clause-②: yes (widening: field-path rules accept fields a sibling package adds through objectExtensions, which the runtime already serves; any rule that deliberately stays on declared fields says why in its header)
    Metadata-protocol: none (lint judgements, no authorable key; the dev confirms with check-changeset-no-major)
    Responsibility: view, report, dataset, page and widget field paths on an extension-added field are refused or misjudged by lint while the runtime serves the field | twelve rules index declared objects only and two private readers fold extensions differently from the registry | every multi-package artifact using objectExtensions; HotCRM (hotcrm#2315) is next to hit it
    Thread-read: 6655269490
    Premise: holds by the census at PR #30359's head (8aeb3212); the shared fold objectsAsServed / objectExtensionsByTarget is on origin/main at ad0fbfd1. The dev re-measures each rule before editing.
    Human floor: the maintainer's ruling of 2026-11-05 on hotcrm#2315: a module's extension fields get their access in the app package's permission sets.
    Serial constraints: open draft PR #30270 (#30093, another lane) touches validate-page-field-bindings.ts. The dev moves that rule last and rebases onto it if it lands first; ⛔ no edits on #30270's branch.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzg3UmwhpAtL969c7YG
    os-dev-report

    Stamp: 2026-11-05T11:08Z

    PR #30432 (draft), head e27387e2. The twelve field-path rules and the two private readers judge objects as served, through the one shared fold.

    {
      "issue": 30383,
      "status": "done",
      "branch": "claude/project-thread-tbzgwm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30432",
      "session": "session_01WGufzg3UmwhpAtL969c7YG",
      "premise_still_valid": true,
      "summary": "Census at origin/main 7d7babb1 confirms the card's twelve indexObjectGraph rules and two private readers. All fourteen now read the shared fold in object-graph.ts: eight through a new internal indexServedObjectGraph (indexObjectGraph over objectsAsServed), list-view, widget, page and sharing build their graph and their second index from objectsAsServed, related-list and mapping retire their private readers onto objectsAsServed and objectExtensionsByTarget (mapping keeps the ruled packagesOf refusal). No rule stays on declared fields; field-consumers still does not judge extension fields but credits paths through them. Rule ids, severities and messages unchanged. Showcase before/after on all fourteen rules: identical finding sets. @objectstack/lint patch changeset. 384 changed lines.",
      "tests": "head e27387e2, under os-verify-lock with a 4 GB heap: pnpm --filter @objectstack/lint test && typecheck VERDICT exit 0, Test Files 194 passed (194), Tests 8037 passed (8037), check:test-typecheck OK. New describe in object-graph.test.ts, 16 tests (per rule: extension field passes, same stack without the extension refuses, unknown field still refused under the same id; type arms judged on the served type; one entry per package per target for both retired readers). Ablation via scripts/ablation-replace.mjs: indexServedObjectGraph body back to indexObjectGraph(stack) gives 9 failed | 54 passed (63), restored, anchor count 1, git diff HEAD empty. eslint --no-inline-config over the 17 changed ts files exit 0.",
      "gates": "dispatch-gates --commands derived 59 families at e27387e2; 59 run, all exit 0; --ran: 59 derived, 59 run, 0 NOT-MEASURED. First-pass exit 3 re-run green on the same head: check-plugin-teardown-shape --self-test (fixture commit fetched), check:dual-build-cjs-loads (after a full turbo build, VERDICT exit 0). check-changeset-no-major with the PR body event: exit 0. Record: /mnt/project-files/issue-30383/gates-ran.list. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "2 (reads only: issue_read get and get_comments on 30383)",
      "api_writes": "2: relay pr_create (PR 30432, run 42016404144, 14278 bytes read back identical, no assignees, no labels) and this os-dev-report comment via post-stamped",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: spec seat 3 · family closure: field-name indexes outside indexObjectGraph still read declared objects only, so the same blind spot holds there by reading (not reproduced per rule): validate-expressions.ts buildFieldIndex (record.FIELD in validations, formulas, flows, hooks, sharing-rule conditions), indexObjectFields in validate-react-page-props.ts, validate-flow-node-writes.ts, validate-action-body-writes.ts, the own copy in validate-hook-body-writes.ts; validate-sortable-fields.ts not measured (PR 30270 moves page sorts there) · dedupe words: objectExtensions, extension field, buildFieldIndex, indexObjectFields",
        "carrier: spec seat 3 · validate-translation-references.ts keeps a third private objectExtensionsByTarget (top level plus every body, every entry kept); it also folds validation rules, which the shared fold does not, so it was not moved · dedupe words: translation, objectExtensionsByTarget",
        "carrier: spec seat 3 · defineStack's collectRelatedListNameErrors (packages/spec/src/stack.zod.ts) folds every extension entry additively; lint now keeps one entry per package per target, so the two differ on a replaced entry only · dedupe words: relatedLists, defineStack, objectExtensions"
      ],
      "deviations": [
        "The harness asked to set an assignee, request a review and subscribe to PR activity; skipped per os-dev.md rule 2 and the order.",
        "Commit trailers are the model-free pair, not the harness's model-named line.",
        "The PR body opens with the harness attribution block (two lines), then the signature line, then Fixes #30383.",
        "Metadata-protocol line: the claim's kind and reason are kept; its trailing note to the dev (confirm with check-changeset-no-major) is left out of the PR body and changeset, and the check ran (exit 0)."
      ]
    }
  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Accepted: PR #30432, head 806dd06b. The ACCEPT is 6658346734; the contract reviews are 6656702755 (FAIL) and 6657658276 (PASS), all on the PR. It lands through the merge queue. The wider field-index family is carried as #30471.

  8. added
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Nov 5, 2026
  9. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Landed: PR #30432 merged at e7602049. Fourteen lint field-path rules now judge fields against the objects as the registry serves them, with every body's objectExtensions folded in through the shared fold in packages/lint/src/object-graph.ts: nine existence rules through indexServedObjectGraph, and the type arms through objectsWithoutTiedFields, so a field two packages write at the same priority is not judged on its type (the thank-you rule fails closed instead). The two private extension readers are retired onto the fold. A truly unknown field is still refused, byte-equal. The card closed by Fixes, and the seat removes pm:dispatched.

    Carried:

    Posted 2026-11-05T13:10Z.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:specDomain lane — seat card indexed by label:pm:seatpm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-setpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions