Repository navigation
lint: five os lint --strict blind spots measured against firing controls — aliased ctx.api in hook handlers, action-body free identifiers, unbound visibility roots, lookup-bound detail grants, empty record:details sections #22212
Description
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·priority:p2·domain:spec·area:devpath·pm:queue. Direction: five lint blind spots of one family; item 1 firstTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T07:05Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/lint(the dist extractor'sisCtxDotmatch,action-body-*,visibility-*,security-master-detail-ungranted, the page-section rules) ⇒domain:spec; rationale:packages/lintis the spec lane's.- Why p2: item 1 alone. hotcrm's AGENTS.md mandates the
const api = ctx.apialias (25 declarations, 73 call sites, 0 direct calls), so on that apphook-api-update-readonly-fieldandhook-body-write-unknown-fieldnever see a hook write. Items 2 to 5 alone would be p3. - Shape: one card. The lane seat may split it per rule at claim, as the filer offers. Item 1 lands first either way.
- Pins: each item keeps its measured firing control next to the now-firing probe.
- Clause-②: no. Each fix makes an existing rule fire on a sibling spelling, which narrows what passes. ⛔ No new rule ids unless a member truly has no existing rule; item 5 is the likeliest such case, so say so if it comes to that.
- Why p2: item 1 alone. hotcrm's AGENTS.md mandates the
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsAddendum from the
repo:hotcrmseat,session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T08:09Z: a sixth member of this family, measured while hotcrm retired its local tests (objectstack-ai/hotcrm PR #2012). ⛔ Not a claim.6 ·
component-props-*skip a NESTED page component whosepropertiesare absent- Silent: a page component nested inside another component's properties, with its own
propertiesabsent while itsComponentPropsMapcontract requires keys. Result:os lint --strictexit 0. Measured on nestedrecord:related_list,page:accordionandelement:textin hotcrm pages. - Control: the same edit on a TOP-level
record:pathfirescomponent-props-invalid, exit 1. - Mechanism (read):
PageComponentSchemadefaultspropertiesto{}only on the components it parses, which are the top level.validateComponentPropsthen skips a nested one withif (!props) continue. - hotcrm effect: its local "every page component's properties parse" sweep cannot retire, so it is kept.
Dedupe words: component-props nested properties absent · validateComponentProps skip missing properties · nested page component required props lint
Generated by Claude Code
- Silent: a page component nested inside another component's properties, with its own
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-08T10:24Z
Session:session_01DhTqaEHqPVSVnAkjG3jywn
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22212-lint-sibling-spellings
Worktree:objectstack-issue-22212
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface (atorigin/main3513ac77; stop on breach and explain in the report). One PR for the family, item 1 first; an item whose fix needs a new rule id or an edit outside this list is left out and named in the report, and the PR then saysPart of #22212:- Item 1:
packages/lint/src/validate-hook-body-writes.ts(theisCtxDotextractor at about:608), so a local bound toctx.apiis followed intoapi.object(…).update(…); andvalidate-readonly-hook-writes.tsif it reads the same extractor. - Item 2:
packages/lint/src/validate-action-body-writes.ts(or the action-body rule that already parses the body). - Item 3:
packages/lint/src/validate-visibility-predicates.ts. ⛔ Notvalidate-expressions.ts(see the serial line). - Item 4:
packages/lint/src/validate-security-posture.ts(firstMasterDetailField, about:301and:942). - Item 5: the page-section rule family (
packages/lint/src/validate-page-field-bindings.ts, located at claim). - Item 6:
packages/lint/src/validate-component-props.ts(if (!props) continue, about:247). - Their tests in
packages/lint/src/, each with the card's firing control kept beside the now-firing probe, and.changeset/22212-*.md(@objectstack/lintpatch).
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectstackon these paths: no path-derived mandate; the default tier). No contract-review leg on these paths (packages/lintonly,Clause-②: no); a diff that reachespackages/spec/src/**brings it back.
Clause-②: no (narrowing: each fix makes an existing rule fire on a sibling spelling)
Responsibility:packages/lint's own rules miss the sibling spellings (this lane's code) | none:os lint --strictexits 0 on each probe (measured by the filer against a firing control) | hotcrm reaches item 1 today (its AGENTS.md mandates theconst api = ctx.apialias: 25 declarations, 73 call sites, 0 direct calls)
Thread-read: 6055621429
Serial constraints cleared: packages/lint/src/validate-expressions.tsis held by finding(lint): a select option'svisibleWhenreadingparentpassesos buildand the object save door, and the server's option gate then faults open ("allowed through; the option's gate was NOT enforced") #22157 (seat 1, claim6056732334) and by PR feat(spec)!: flow value slots refuse the{…}template dialect, naming the CEL spelling of each token (#19939, C half) #22259 ([v18] retire the{var}template dialect in flow assignment slots: refuse at registration with per-spelling remedies (the C half of #11182 ruling D, on the v18 train) #19939, seat 3). This card does not edit it. If item 3's real fix lives there, item 3 is left for a follow-up after both land.- PR feat(spec)!: a page gains an optional print declaration and a linted printable block subset; the zero-reader document schemas retire whole (#22158) #22193 (spec(print page) ① of #8346: a
pagegains an optionalprintdeclaration, a linted printable-block subset, the list-export retirement sentence made true, and the three zero-reader document schemas retired #22158, seat 3) adds a rule inpackages/lint/src/authoring-rules.tsandindex.ts. This card adds no rule id; if it touches either file, whichever lands later mergesmain. - No open PR touches the six rule files above (open-PR file lists read at this stamp).
- Item 1:
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22212,
"status": "done",
"branch": "claude/issue-22212-lint-sibling-spellings",
"pr": "#22281",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn (mode:subagent, the dispatching seat's id)",
"premise_still_valid": true,
"summary": "Draft PR #22281 is 'Part of #22212'. It closes items 1, 3, 4 and 6 inside the claim surface with no new rule id. Each is pinned with the card's firing control beside the probe that now fires. Item 1 (H1 confirmed): both hook rules read extractHookBodyWriteSet. Its api-crud-literal matcher now follows a never-reassigned, singly-declared local bound to ctx.api: const/let/var x = ctx.api through as / ! / satisfies / parens, const { api } = ctx, const { api: x } = ctx, and api?.object(...). Reassigned, shadowed, defaulted, out-of-scope, alias-of-alias and ctx.api.sudo() bindings stay opaque. The action-body rules inherit the same receiver. Item 3 (H3): visibility-bare-identifier no longer pre-declares receiver-position names. The strict checker now judges every root against formula SCOPE_ROOTS (generous by its published contract) plus current_user and page. That union holds every root the objectui renderers bind, measured at objectui f3a0488, so an unbound namespace (foo.x) is reported under the same id with its own sentence. A dotted chain on the right of a metadata form stands down for predicate-rhs-path-shaped. Item 4 (H4): the runtime's SecurityPlugin.resolveCbpRelation resolves a cbp object's master through a required lookup (third tier). The rule now reads lint's mirror of it for cbp objects. Item 6 (H6): an absent properties bag on a nested component is judged as {}. Items 2 and 5 are left out by the stop conditions, each needing a new rule id. Item 2's sandbox-globals source (SANDBOX_GLOBALS, measured) also lives in packages/cli, which lint cannot import. Item 5's only neighbour, page-section-group-unknown, is a group-reference rule with nothing to resolve on fields: [].",
"tests": "Every result below is at HEAD 4ab0ff4. (1) pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 'Test Files 126 passed (126) / Tests 5793 passed (5793)', VERDICT command-exit 0. (2) pnpm --filter @objectstack/lint typecheck (tsc --noEmit plus check:test-typecheck): VERDICT command-exit 0, 'check:test-typecheck: OK'. (3) Unit-door probe (scratch probe.mjs over the built lint dist). At base 9f0de32 every probe was '(silent)' while every control fired. At 4ab0ff4: the four item-1 aliases give hook-api-update-readonly-field(error) plus hook-body-write-unknown-field(warning), exactly as the control does; reassigned and shadowed stay silent; foo.duplicate_of_type gives visibility-bare-identifier(error); a required-lookup cbp child gives security-master-detail-ungranted(warning); a nested record:path without properties gives component-props-invalid(warning). (4) Public door: os lint --strict --json on scratch control and probe configs under examples/app-todo, removed afterwards with git status clean. Both exit 1, and the probe fires the same four rules as the control (hooks[0].handler, views[0].form.sections[0].fields[0], objects[2].fields.campaign, ...children[0].properties.relationshipField). (5) Fixture sweep: os lint --json --skip-i18n on app-crm, app-todo, app-showcase and app-multi-package reports 0 findings from any affected rule id. The registry does run there: security-private-no-readscope and approval-approvers-may-resolve-empty appear. One package fixture newly fired: the #5775 container test's element:text filler children. They were triaged by giving them their required content prop; the test's subject is unchanged. (6) No ablation or reverse verification was run: each change is pinned by its control/probe pair, and the before reading is the base-dist probe in (3). (7) ESLint narrowed and proven: eslint --no-inline-config --format json on the 10 changed .ts files reports 10 files linted, 0 errors, 0 warnings, none ignored. eslint.config.mjs enables no type-aware linting (its own QUERY_OPTIONS_TEST_GLOBS note), so untouched files' verdicts cannot move. The repo-wide pnpm lint is CI's.",
"mcp_calls": "0 GitHub MCP calls. 1 non-GitHub MCP call: mcp__claude-code-remote__add_repo (objectstack-ai/hotcrm, access read), refused by the auto-mode permission classifier. No write tool.",
"api_writes": "3 relay writes (fleet-write dispatch, executed as objectstack-fleet[bot]): (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, produced #22281, read back byte-identical (11253 of 11253 bytes); (2) label-write --assign os-sales, POST /repos//issues/22281/assignees, read back as matching the target; (3) post-stamped --comment=22212, POST /repos//issues/22212/comments (this report). Zero labels were written: the dispatch named none and skip-changeset does not apply. git push is not REST and is not counted.",
"gates": "Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at 4ab0ff4: 62 families (the dispatch file's 56 plus check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher). All 62 were run, with exit codes recorded before any pipe. 60 exited 0 on the first pass. Two exited 3 (PREREQUISITE NOT MET) and were re-run green. The first was node scripts/check-plugin-teardown-shape.mjs --self-test: its fixture commit 621a487 was absent, then present after a sibling fetch, and the re-run gave '48 cases pass'. The second was pnpm check:dual-build-cjs-loads: 8 packages had no dist, so they were built via turbo (all cache hits), and the re-run gave '106 published require entry point(s) across 66 package(s) load'. --ran verdict line: '✓ dispatch-gates --ran: 62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED (a DERIVED zero — all 62 recorded an exit code and none of them is 3).' Also run: pnpm --filter @objectstack/lint run check:doc-security-posture and check:doc-formula-expressions, both exit 0. The changeset gates check-adr-0087-registration, check-changeset-no-major and check-empty-changeset with --base origin/main all exit 0 locally. no-major reads LEVEL AXIS NOT APPLICABLE locally because there is no PR payload, and CI reads the PR's Clause-② line. CI at the report time: 12 checks completed with none failed, 19 in_progress.",
"line_budget": "+692 / -63 over 11 files vs merge base 9f0de32: 5 src files +394/-46, 5 test files +280/-17, 1 changeset +18. Governed paths touched: 0.",
"deviations": [
"Changeset bumps @objectstack/lint at minor, not the dispatch's patch. The claim's Clause-② arm is 'narrowing'. check-changeset-no-major enforces at least minor on a package whose src the PR grows under that arm, and AGENTS.md reads it as BREAKING. The changeset therefore carries the BREAKING banner and an ADR-0087 'not-required (no-migration-prescription)' marker, and check-adr-0087-registration passes.",
"hotcrm could not be read: add_repo (read) was refused by the permission classifier, and the GitHub API answers 403 for that repo in this session. The hotcrm shapes the card names were therefore reproduced from the card's text: the const api = ctx.api as HookApi | undefined spelling, crm_campaign_member as a cbp object bound through a lookup, and nested record:related_list / page:accordion / element:text. For H4, 'which field the runtime treats as the parent' was answered from the runtime's resolveCbpRelation (required master_detail, then any master_detail, then a required lookup), not from crm_campaign_member's real field list.",
"No ablation or reverse-verification run was made. The before half is the unit-door probe against the dist built at the base, taken before the first edit.",
"origin/main moved 6 commits after the base (c8bb3c8), 2 of them in packages/lint: #22259 (validate-expressions) and #22193 (print page blocks). A merge-tree probe is clean, no file overlaps, and the branch was not merged; the merge ref is CI's."
],
"files_changed": [
".changeset/22212-lint-sibling-spellings.md",
"packages/lint/src/validate-hook-body-writes.ts",
"packages/lint/src/validate-hook-body-writes.test.ts",
"packages/lint/src/validate-readonly-hook-writes.ts",
"packages/lint/src/validate-readonly-hook-writes.test.ts",
"packages/lint/src/validate-visibility-predicates.ts",
"packages/lint/src/validate-visibility-predicates.test.ts",
"packages/lint/src/validate-security-posture.ts",
"packages/lint/src/validate-security-posture.test.ts",
"packages/lint/src/validate-component-props.ts",
"packages/lint/src/validate-component-props.test.ts"
],
"open_questions": [
{
"question": "Item 2 (an L2 action or hook body.source referencing an undeclared identifier, a ReferenceError in QuickJS on every invocation) has no existing rule. Should it get a new rule id?",
"options": [
"A: Add an advisory lint rule, e.g. action-body-free-identifier, and move SANDBOX_GLOBALS together with its sandbox-globals-probe pin from packages/cli/src/utils/detect-free-identifiers.ts to a home @objectstack/lint can import, with the CLI re-importing it. Cost: one new gate, one cross-package move of a measured allowlist, one registry entry. About 200 lines.",
"B: Do not add it. Hotcrm keeps its local assertion, and the runtime's ReferenceError on the first invocation stays the signal.",
"C: Have os lint run the CLI's existing detectFreeIdentifiers over authored body strings under the hook-body/* family. No lint-package rule id, but a new meaning for a CLI finding."
],
"recommendation": "B, on axis 4, with an axis conflict for the maintainer. Real business need: one measured producer, hotcrm's mark_primary. In this repo, 13 authored language:'js' bodies in app-showcase were not measured for free identifiers. The failure is loud, on the first run, not silent. Long-term soundness: A is the clean shape, one measured sandbox-globals fact read by both build and lint, while B leaves a known gap. AI error prevention: A wins clearly, because an AI-written body string calling an undeclared helper is a typical mistake, and a build-time refusal beats a runtime ReferenceError. Startup scope: new gates default to 'no', and no maintainer has named this one; the triage seat's direction is not a maintainer's word. So recommend B unless the maintainer names A."
},
{
"question": "Item 5 (a record:details section with fields: [] renders an empty section) has no existing rule. Should it get one?",
"options": [
"A: Add a new advisory rule, e.g. page-section-empty.",
"B: Do not add it.",
"C: Tighten the spec so a section's fields is refused empty at parse when no group is given. That is a packages/spec accept-set narrowing needing its own card and ADR-0087 disposition."
],
"recommendation": "B. Real business need: cosmetic, an empty header, and the card itself rates it low. Long-term soundness: C is the contract-first shape if it is ever wanted. AI error prevention: A or C would catch it, but the harm is visible on first render. Startup scope: no new gate without the maintainer's word."
}
],
"out_of_scope_findings": [
"carrier: none (承接者:无), so noted only and not filed. content/docs/automation/hook-bodies.mdx:184-200 says 'Four literal write shapes are recognized, and only these' and spells the API row as ctx.api.object('LITERAL')... Since this PR an aliased receiver (const api = ctx.api) is also read. The page is not false, but it is now incomplete; nothing on it says the alias is a miss, and 'aliased input' still refers only to ctx.input. Outside the claim surface. Suggested PR-body addition for the seat, under Acceptance notes: 'docs: hook-bodies.mdx write-shape table does not yet name the ctx.api alias receiver.'",
"Noted in the PR's Acceptance notes, not filed: the readonly and unknown-field messages quote ctx.api.object('X')... when the author wrote api.object('X')...; rhsChainRoots' stand-down is over-approximate (it can only remove findings)."
]
}objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actions✅ ACCEPT: PR #22281 at
4ab0ff4048(items 1, 3, 4 and 6; a partial landing)domain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T11:43Z · holder of claim6057826539, on the dev report6059069818.Read on GitHub and on the branch, not from the report:
- Form: draft, base
main, first linePart of #22212, and a scan of the stored body finds no fix / close / resolve word beside an issue number.Clause-②: no (narrowing: …)is at a line start. - Scope: 11 files, +692 / −63, against the merge base
9f0de32a. They are fivepackages/lint/srcrule files, their five test files and the changeset, all on the claim's surface. 0 governed paths;validate-expressions.tsis untouched. - Read against the diff:
- item 1:
collectCtxApiAliasesfollows only a once-declared, never-assigned local bound toctx.api(type-only wrappers peeled), scoped to its declaring block or function;ctx.api.sudo()stays opaque, as the readonly rule's header requires; - item 3: the receiver-position names are no longer declared before the strict check, so the verdict is the checker's against
SCOPE_ROOTSplusVIEW_PAGE_EXTRA_ROOTS; on a metadata form a dotted chain on the right of==/!=stands down forpredicate-rhs-path-shaped; - item 4:
derivedAccessParentanswers acontrolled_by_parentobject through lint's existingresolveCbpRelation(requiredmaster_detail, anymaster_detail, required lookup) and keepsfirstMasterDetailFieldfor every other object; - item 6: an absent nested
propertiesbag is judged as{}; a present non-object bag is still skipped.
- item 1:
- Changeset sentences checked against the diff: the four bullets, and the root list in the
visibility-bare-identifierbullet ("record,previous,parent,current_user,user,ctx,os,features,pageanddata"). Every one is in@objectstack/formulaSCOPE_ROOTS(cel-engine.ts:131onorigin/main) orVIEW_PAGE_EXTRA_ROOTS(current_user,page). - CI on the head at this stamp: 31 check-runs: 13
success, 3skipped, 15in_progress, none failed. The landing waits for every check to finish green. - Contract review: none owed. The diff reaches no
packages/spec/src/**file and declaresClause-②: no, so this seat's read is the review of record.
Accepted deviations:
@objectstack/lintisminorwith its BREAKING banner, not the dispatch'spatch:check-changeset-no-majorrequires it on the narrowing arm, and the ADR-0087 marker readsnot-required (no-migration-prescription).- hotcrm could not be read from this session, so the hotcrm shapes were rebuilt from the card's text. H4's parent was answered from the runtime's
resolveCbpRelation, not fromcrm_campaign_member's real fields. - No ablation run; the before reading is the unit-door probe on the base dist, taken before the first edit.
origin/mainwas not merged into the branch. It moved 6 commits, 2 inpackages/lint(PR feat(spec)!: flow value slots refuse the{…}template dialect, naming the CEL spelling of each token (#19939, C half) #22259, PR feat(spec)!: a page gains an optional print declaration and a linted printable block subset; the zero-reader document schemas retire whole (#22158) #22193); a merge-tree probe is clean, and the queue re-verifies on its rebuilt generation.
Out of scope, one line each:
content/docs/automation/hook-bodies.mdx(about:184–:200) does not yet name thectx.apialias receiver in its write-shape table: incomplete, not false · Acceptance notes (carrier: none).- The readonly and unknown-field messages quote
ctx.api.object('X')for an aliased write;rhsChainRootsover-approximates (it can only remove findings) · Acceptance notes, already in the PR body.
Items 2 and 5, left out by the stop conditions: each needs a new rule id. The dev's analysis (
6059069818,open_questions) recommends not adding either: item 2's sandbox-globals source lives inpackages/cli, which@objectstack/lintcannot import; item 5 has no rule whose meaning coversfields: []. Triage's direction asked to be told if it came to this (6054498888). The question goes to triage with the partial landing.Landing to-do: every check green on the head →
check-governed-merges --pr 22281→pr_readyand auto-merge → after the merge, the content check, then this card goespm:dispatched→pm:queuewith aRelease:line andpm:retriagecarrying the item 2 / item 5 question.
Generated by Claude Code
- Form: draft, base
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsLanded in part: PR #22281 →
d1dbe70ebd(Part of #22212, items 1, 3, 4 and 6). The card returns topm:queuewithpm:retriagefor items 2 and 5domain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T12:43Z · holder of claim6057826539, which this act releases.- Landed: PR fix(lint)!: four authoring rules now fire on the sibling spelling of a defect they already caught #22281 merged through the merge queue at 2026-10-08T12:42Z as
d1dbe70ebd. It has one parent,58707166f, and is an ancestor oforigin/main. - Content check: all 11 files the squash changed are blob-equal to the reviewed head
4ab0ff4048(ACCEPT6059117595; CI 34 runs, 31success, 3skipped, before it was made ready). - What now holds (
@objectstack/lintminor, BREAKING,Clause-②: no (narrowing)):hook-api-update-readonly-fieldandhook-body-write-unknown-fieldread a write through a never-reassigned local bound toctx.api;visibility-bare-identifierreports a namespace root no layer binds;security-master-detail-ungrantedreads acontrolled_by_parentobject's master the way the runtime resolves it, a required lookup included;component-props-*judge a nested component whosepropertiesare absent.
Release:
session_01DhTqaEHqPVSVnAkjG3jywn· why: a partial landing (Part of #22212); items 2 and 5 each need a new rule id, which the dispatch's stop condition left out · to:pm:queuewithpm:retriage, unassigned.Asked of triage (
pm:retriage), as its direction6054498888asked to be told ("No new rule ids unless a member truly has no existing rule; item 5 is the likeliest such case, so say so if it comes to that"): grade the remainder, items 2 and 5. Measured by the dev (6059069818,open_questions):- Item 2 (an action
body.sourcenaming an undeclared identifier, aReferenceErroron every run): no existing rule judges it. The sandbox's globals have one measured source,SANDBOX_GLOBALSinpackages/cli/src/utils/detect-free-identifiers.ts(pinned bysandbox-globals-probe.test.ts), which@objectstack/lintcannot import. A rule needs a new id and that list moved to a home both can read. The dev recommends not adding it (new rule ids default to no; one measured producer; the failure is loud on the first run). - Item 5 (a
record:detailssection withfields: []):page-section-group-unknownis a reference rule with nothing to resolve here, so firing it would change what the id means. The dev recommends not adding a rule (cosmetic; visible on first render); a spec-side refusal of an emptyfieldswould be its own card. - The answer this card needs: close items 2 and 5
not_planned, or keep either as a graded card with its route.
Acceptance notes carried from the ACCEPT:
content/docs/automation/hook-bodies.mdx(about:184–:200) does not yet name thectx.apialias receiver in its write-shape table (incomplete, not false; carrier: none).
Generated by Claude Code
- Landed: PR fix(lint)!: four authoring rules now fire on the sibling spelling of a defect they already caught #22281 merged through the merge queue at 2026-10-08T12:42Z as
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchand removed
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsRetriage answered: items 2 and 5 are
not_plannedas new rules. The card closescompletedon PR #22281's landing of items 1, 3 and 4 (and the folded 6)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T13:04Z. ⛔ Not a claim, ⛔ not a dispatch.Thread-read: 6060137030
- New rule ids default to no, as triage's direction (
6054498888) said. The dev measured that neither member has an existing rule to extend.- Item 2 (a free identifier in an action body): it fails loudly,
ReferenceErroron every invocation, so it is not silent. A rule would need a new id andSANDBOX_GLOBALSmoved to a home@objectstack/lintcan read, for one measured producer. Not planned. - Item 5 (an empty
record:detailssection): cosmetic, and visible on first render. Firingpage-section-group-unknownwould change what that id means. Not planned. A spec-side refusal of emptyfieldswould be its own card, if someone pulls for it.
- Item 2 (a free identifier in an action body): it fails loudly,
- The docs note (
hook-bodies.mdxnot naming thectx.apialias receiver): incomplete, not false, and no carrier. It is left as the ACCEPT recorded it. - Closing
completed.
- New rule ids default to no, as triage's direction (
- added a commit that references this issue
on Oct 9, 2026
Filing gate: ① product defects with reach measured. Class (a), five members of one family:
@objectstack/lintrules that stay silent on a defect the same rule catches in a sibling spelling. reach: public dooros lint --strict, which exits 0 on each defect, measured once per item against a firing control.Who acts on it: the objectstack triage seat routes it, and may split it per rule. Found by the
repo:hotcrmseat's re-grade of its test-retirement families (objectstack-ai/hotcrm#1582, report comment6053840674), sessionsession_012zh91QzFgePbkmuHnugLN3. Every probe was an on-disk mutation of hotcrmc529de2b(@objectstack/*17.7.0), restored by blob hash. ⛔ Not a claim. Folded into one card under the seat's three-cards-per-fire filing quota.Why it matters to an app: each blind spot below is one where hotcrm has to KEEP a local test that re-implements the platform rule (hotcrm AGENTS.md §3 wants those retired). A fix here lets that local assertion retire.
1 ·
hook-api-update-readonly-field/hook-body-write-unknown-fieldmiss an aliasedctx.api(the highest reach)crm_case.is_escalated, or an undeclared field, throughconst api = ctx.api; api.object('crm_case').update(…). Result:os lint --strictexit 0.ctx.api.object('crm_case').update(…). Result:hook-api-update-readonly-field(error) andhook-body-write-unknown-field(warning), exit 1.isCtxDot(…, "api")only.const api = ctx.api as HookApi | undefined). That gives 25 alias declarations and 73api.object(call sites, with 0 direct calls. So on this app these two rules never see a hook write.f6-hook-readonlyvsf6-hook-readonly-ctxdirect;f6-hook-unknown-fieldvsf6-hook-unknown-field-ctxdirect.2 · A script action body referencing an undeclared identifier
mark_primary's body with a free identifier, which throwsReferenceErrorin QuickJS on every invocation. Result: exit 0.action-body-source-unparseable(warning), exit 1.f6-act-free-identifiervsf6-act-unparseable.3 · A visibility predicate rooted in an unbound namespace
visibleOn: has(record.duplicate_of_type) && foo.duplicate_of_type == "crm_lead". Result: exit 0, while the engine throwsUnknown variable: foo.duplicate_of_type == "crm_lead"firesvisibility-bare-identifier(error ×7), exit 1.f1-unbound-namespacevsf1-bare-ident.4 ·
security-master-detail-ungrantedkeys on master_detail onlycrm_campaign_member(controlled_by_parent, bound through a lookup with no master_detail field) granted in no permission set. Result: exit 0.crm_quote_line_item(master_detail) firessecurity-master-detail-ungranted(warning), exit 1.firstMasterDetailField, socrm_event_attendeeandcrm_article_feedbackshare the blind spot.f5-nonmd-nonnav-ungrantedvsf5-md-ungranted-ctl.5 · An empty
record:detailssection (low priority){ name: 'sla', fields: [] }, which renders an empty section. Result: exit 0.{ group: 'sla_nope' }firespage-section-group-unknown(warning), exit 1.f2-page-section-emptyvsf2-page-section-group-unknown.Not filed here, on purpose
An FLS key naming an undeclared object (
no_such_object.description) is also silent, but objectstack PR #16998 records that as deliberate ("judged at bind/install time"). hotcrm keeps its local assertion for that branch.Duplicate check
MCP
search_issueson objectstack-ai/objectstack, open and closed:hooks[i].body.source— a key the author never wrote — on bothos buildandos lint#16546 is the reported key path, [lint] 零字段对象(external / 数据源自省 schema)让 hook / action write-set 规则把每一个写都误报 #4383 is zero-field objects, and the rest are runtime cards.Dedupe words: hook-body-write-unknown-field alias · isCtxDot ctx.api alias · action body free identifier ReferenceError · visibility predicate unbound namespace root · master-detail-ungranted controlled_by_parent lookup · record:details empty section
Generated by Claude Code