Skip to content

feat(lint): check:doc-security-posture — gate os:check docs object examples on the os validate security posture - #10930

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10618-oscheck-validate-gate
Aug 21, 2026
Merged

feat(lint): check:doc-security-posture — gate os:check docs object examples on the os validate security posture#10930
os-elon merged 1 commit into
mainfrom
claude/issue-10618-oscheck-validate-gate

Conversation

@os-elon

@os-elon os-elon commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes #10618

check:skill-examples proves an os:check docs block compiles; nothing proved os validate accepts what the block constructs. #10581 measured the consequence: 22 ObjectSchema.create() examples omitted sharingModel — the omission type-checks clean (.optional(), no .default()), while os validate rejects it as severity: 'error' (SECURITY_OWD_UNSET). Sweep #10714 fixed those 22 instances; this PR closes the class: a new gate, check:doc-security-posture, runs the same author-time lint rule os validate runs over every ObjectSchema.create literal in an os:check-marked content/docs/** block.

Design judgments (each is mine, per the card; reasoning inline)

1. Which lint rules run: validateSecurityPosture, error severity only — measured, not assumed.
The corpus census (25 .create calls in 191 marked blocks across 189 files) plus the rule inventory settled the set:

  • validateSecurityPosture is the registry's gating-tier entry for exactly the measured class, and its four error rules that judge a lone object document (security-owd-unset, security-owd-alias, security-external-wider-than-internal, security-controlled-by-parent-no-relation) are all self-contained properties of the literal — no cross-stack reference a single-page example could false-red on. Its warning/info rules need permission sets / books a doc example never carries and stay structurally silent (permissionSets.length > 0 guards).
  • Not the full os validate rule set: reference-integrity rules judge a whole stack, and a lone teaching literal legitimately references objects the page does not declare — firing them is the false red that teaches people to add ignores.
  • Not validateSecurityRoleWord: the repo-level check:role-word ratchet already owns that vocabulary on the docs surface; a second gate adjudicating the same word is a second opinion (Prime Directive Add comprehensive test suite for Zod schema validation #12).
  • Parity is by import, never reimplementation: the verdict is validateSecurityPosture from @objectstack/lint — the identical registry entry os validate / os compile and the runtime publish gate run. The rule's own docblock blesses pre-parse input ("works both pre- and post-zod-parse"), and none of the keys it reads on this path carries a zod default, so the pre-parse judgment equals the post-parse one.

2. How the literal is obtained: static extraction with per-fact conservatism — never execution.
Same choice as the existing gate (check:skill-examples runs tsc, executes nothing) and its CEL sibling (check-doc-formula-expressions: "only statically-extractable sources"). The evaluator resolves literals, arrays, nested objects, templates-without-substitution; anything else (a Field.text({...}) factory call, an identifier) becomes an opaque non-null, non-string sentinel, and each rule verdict is trusted only where its facts are statically known:

  • unset can only fire on a key that is textually absent (a present-but-dynamic value is the sentinel — non-null);
  • alias/wider only fire on static strings (the rule itself requires typeof === 'string');
  • cbp-no-relation reads the fields subtree, so it is suppressed with a printed notice when that subtree is not fully static — a Field.masterDetail(...) call must not read as "no relation". (19 of the 22 corpus literals have factory-call fields; this is what keeps the gate at zero false positives without shrinking its population.)

Two shapes are refused loudly rather than skipped (absence must be loud): a non-object-literal argument / top-level spread, and a non-static name (the sys-object exemption needs it). Zero of either in today's corpus.

3. How a violation prints. page.mdx:line object "name" [rule-id], then the rule's own message and hint verbatim (the same text os validate prints), then one paragraph stating the parity: os validate runs this same rule and fails the copied app — fix the example, don't weaken the rule or drop the marker.

4. Per-block opt-out: an in-script exemption registry, not a new in-doc marker. EXEMPT_SITES follows the check-doc-formula-expressions EXEMPT_EXAMPLES house discipline — an entry names the site AND its reason, and is self-invalidating in both directions (stale entry = error, entry on a now-clean site = error; both pinned in the self-test). Born empty: no legitimate teaching example needed one (see the born-green measurement). A new in-doc marker syntax was considered and rejected: it would interact with check-skill-examples' orphan-marker guard and MDX comment handling for zero present-day benefit.

5. Gate home: packages/lint/scripts/, not a packages/spec/scripts/ sibling — a declared deviation from the claim surface. The verdict lives in @objectstack/lint, and packages/spec cannot import it: lint depends on spec, so a reverse devDependency cycles the turbo build graph. The repo has already ruled where this shape lives — check:doc-formula-expressions' own CI comment: "its home in packages/lint (which owns that verdict)". Wired identically to that precedent: package script (self-test first), CI step in the same post-build typecheck-consumers job, directly beside its CEL sibling. Declared on the issue in a claim-amendment comment before the PR.

Born green — with 8 more instances found and fixed (bounded in-class fixes, declared)

The dispatch's born-green premise held for #10581's audited 12 pages (sweep #10714, merged), but the gate's first full run found 8 more ObjectSchema.create sites missing sharingModel on pages outside that audit's scope — same defect, never measured before:

Page object fix
plugins/development.mdx:135 greeting_log sharingModel: 'private'
protocol/kernel/i18n-standard.mdx:657 account 'private'
protocol/kernel/lifecycle.mdx:482 salesforce_account 'private'
protocol/kernel/plugin-spec.mdx:264 account 'private'
protocol/objectql/schema.mdx:53 customer 'private'
protocol/objectql/schema.mdx:137 project 'private'
protocol/objectql/state-machine.mdx:31 purchase_request 'private'
ui/field-grouping-and-order.mdx:36 showcase_contact 'private' — mirrors the real showcase_contact (examples/app-showcase/src/data/objects/contact.object.ts:37)

Why fixed here rather than exempted or deferred: exempting live instances of the gate's own class at birth is the "gate that lies" the card forbids; the fix shape is pinned by the merged #10714 sweep (explicit sharingModel directly after name:; 'private' is the rule's own recommended default); no competing claim existed on any of these files (git ls-remote branches + open-issue search); and the claim surface was amended on #10618 in the same round, before this PR. The two deliberate incorrect-counter-examples in metadata-driven.mdx already carry sharingModel post-sweep, so no opt-out was needed for them either.

Full run readout at HEAD 39dcc1a3a1 (after the 8 fixes):

22 ObjectSchema.create example(s) in 191 marked block(s) across 189 docs file(s) carry an os validate-clean security posture

(22 judged sites = 25 .create calls minus 3 non-ObjectSchema factories — App.create, Action.create, Dashboard.create — which carry no OWD posture and are deliberately not read.)

Positive control — real corpus, mutation proven on disk

Reverse verification ran from the committed state. Mutation: remove the just-added sharingModel: 'private', from plugins/development.mdx (perl -0pi), confirmed on disk by anchored grep counts — sharingModel occurrences in the file 1 → 0 — never by the editor's exit code. No rebuild is involved in either leg, checked rather than assumed: the gate reads content/docs/*.mdx from source; the only built artifact it consumes (packages/lint/dist) is untouched by the mutation. The gate went red, exit 1 captured before any pipe, naming the exact site:

content/docs/plugins/development.mdx:135  object "greeting_log"  [security-owd-unset]
    custom object "greeting_log" declares no sharingModel (OWD). The runtime fails CLOSED to 'private' ...

Restore leg: git restore --source=HEAD --staged --worktree, anchor count back 0 → 1, git status --porcelain clean, gate green again (exit 0, same 22-site verdict line).

The self-test (house style, runs first in the package script) pins both directions over the real pipeline: the measured defect at the right page line, the alias value, silence on declared postures / factory-call fields / sys_ objects / unmarked blocks / dynamic values / non-ObjectSchema factories, the loud spread refusal, the cbp suppression notice, and the exemption ledger's both-direction discipline.

Changeset

None owed — skip-changeset label applied, re-derived rather than assumed. @objectstack/lint publishes files: ["dist","README.md","CHANGELOG.md"]; scripts/ reaches no published surface, and the only package.json change is a dev-only scripts entry. Docs and workflow publish nothing. Same reasoning as PR #10684 (tooling-only) and PR #10714 (docs-only).

Gates

Derived with node scripts/pm/dispatch-gates.mjs (no paths — it read the change set from merge base 58563be06): 10 paths, 34 matched families. All run locally at HEAD 39dcc1a3a1, every exit captured before any pipe, verdicts from the gates' own lines:

docs family:      check:doc-anchors 0 · check:doc-authoring 0 · check:docs-audit-scope 0 · check:docs-redirects 0
                  check:role-word 0 · check:published-readme-links 0 · check-doc-frontmatter 0
new gate:         check:doc-security-posture 0 (self-test + scan) · check:doc-formula-expressions 0
workflow family:  check:node-version 0 · check:required-contexts 0 (pnpm + patrol script) · check:shard-attestation 0 (pnpm + ci script)
                  check:workflow-status-functions 0 · check-step-collectors 0 · check-aggregator-roster 0
package family:   check:slot-lookup 0 · check:test-source-alias 0 · check:type-source-resolution 0 · check-ci-filter-parity 0
                  check:cross-package-test-inputs 0 (pnpm + ci script) · check-plugin-teardown-shape 0
                  check:type-check-coverage 0 · check:type-check-debt 0 (--re-measure over the full built closure)
deps family:      check:override-consistency 0 · check-changeset-fixed 0 · check-osv-exemptions 0
spec quartet:     check:empty-state 0 · check:liveness 0 · check:strictness-ledger 0 · check:variant-docs 0
reasoned add:     check:skill-examples 0 · check:nul-bytes 0
docs-drift:       docs-audit/check-affected-docs 0

Verdict lines rather than bare exit codes, e.g.:

  • 22 ObjectSchema.create example(s) in 191 marked block(s) across 189 docs file(s) carry an os validate-clean security posture (the new gate)
  • 227 prose examples type-check against @objectstack/spec — all 8 edited blocks still compile with the added key
  • check-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 388.2s, 1908 raw tsc error(s) total, none above its recorded number (its plugin-auth "12 below ceiling" surplus note is pre-existing on main; --lower would touch a ledger outside this card's surface)
  • check-nul-bytes: OK (scanned 6302 text file(s); no raw ASCII control bytes)
  • doc authoring guard: 389 files clean — no bare metadata literals.
  • check-role-word: OK, no new occurrences of the reserved word.

Reasoned additions beyond the derivation (the derived list is a clue, not a spec): check:skill-examples — 8 of my edits are inside blocks that gate compiles (same reasoning as #10714's run, where dispatch-gates also did not name it); check:nul-bytes — runs on any edit.

Narrowings, declared: repo-wide pnpm lint (eslint . --no-inline-config) is CI's run and was not run here.

Out of scope, filed


Generated by Claude Code

…urity posture (#10618)

New check:doc-security-posture in packages/lint: statically evaluates every
ObjectSchema.create literal in a {/* os:check */}-marked content/docs block and
runs validateSecurityPosture over it — the same gating-tier rule os validate
runs — so a docs example that os validate rejects as severity:'error'
(SECURITY_OWD_UNSET et al.) turns a gate red instead of shipping.

Also fixes the 8 pre-existing instances the gate's first run found on pages
outside sweep #10714's audited scope (explicit sharingModel, 'private' per the
rule's own recommended default).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B4h3medzvhB9rpfoja9jcw
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

@os-elon os-elon added tooling skip-changeset PR has no user-facing published change; bypasses the changeset gate labels Aug 21, 2026 — with Claude
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd dependencies Pull requests that update a dependency file labels Aug 21, 2026

os-elon commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Review: ACCEPT (reviewer of record: PM seat domain:spec, session_01B4h3medzvhB9rpfoja9jcw)

Verified against GitHub, not the report:

Clause-②: no — repo tooling; no spec acceptance change; packages/spec/src/** untouched. (Dispatch tier was fable regardless.)

Landing: waiting on CI convergence on 39dcc1a3a1 (Lint & Repo Gates + TypeScript Type Check conclusions — note the new gate's own CI row runs in this very PR, so its first CI execution is part of the readout) → then flip ready + auto-merge. On merge: drop pm:dispatched from #10618, verify closed_by_pull_requests.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32537777627 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.87s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 51 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 926778b Aug 21, 2026
40 checks passed
@os-elon
os-elon deleted the claude/issue-10618-oscheck-validate-gate branch August 21, 2026 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tooling

Projects

None yet

2 participants