lint(security-posture): record which intakes can reach security-owd-alias — measured, annotated, pinned - #16513
Conversation
…alias` `sharingModel` / `externalSharingModel` are closed enums (ADR-0090 D4 / D11), so on every door that parses before the registry runs the alias branches are unreachable by construction. Measured: `defineStack`, the `os validate` / `os compile` schema step, and `saveMetaItem`'s object-schema step all refuse the alias first; `os lint` on a raw object-literal config, `strict: false`, the docs gate and a direct call hand it to the rule, which fires. Annotates the rule's docblock and both alias branches with that intake table, and pins every leg (with its parsed-door control) in `authoring-rule-input-tier.test.ts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
The sibling non-canonical branch emits the same id at the same path; the canonical replacement in the hint is what the alias branch alone adds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 72392eca144e52ae0260168e37c6d7d08497705e && git checkout 72392eca144e52ae0260168e37c6d7d08497705e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a5eccf92577490da8a4ee82285fcdcb5b876c0e1 b8c72ac05256f5318ed79cd7fee5ac28f200eeec && git checkout -B drift-repro a5eccf92577490da8a4ee82285fcdcb5b876c0e1 && git merge --no-ff b8c72ac05256f5318ed79cd7fee5ac28f200eeec
node scripts/docs-audit/affected-docs.mjs --json a5eccf92577490da8a4ee82285fcdcb5b876c0e1
|
Fixes #16109
What this PR is
The card's primary ask was a measurement: which
@objectstack/lintintakes can handvalidateSecurityPostureasharingModelthat never passedObjectSchema.parse? Triage (comment 5556566227) ordered the branches: no such intake, retire the dead branches; such an intake exists, do NOT retire, annotate the rule with its intake and update the coverage table's wording. The measurement found intakes, so this PR takes the annotate branch and changes no behaviour: the rule source diff is comment-only (proved below), plus a pin test that keeps the measurement honest.packages/spec's two enums are untouched.Step 1 — the measurement (branch
b8c72ac052, basec383352cb7)Every leg was taken in one run against the freshly built dist of
spec,formula,sdui-parserandlint(pnpm --filter '@objectstack/lint...' build, exit 0), for each of the fourOWD_ALIAS_FIXkeys (read,read_write,full,public). Theos lintleg was additionally taken end to end with the CLI's real loader:loadConfig(bundle-require, no parse) frompackages/cli/src/utils/config.tson a rawobjectstack.config.ts, then exactly the calllint.tsmakes.security-owd-alias?defineStack(x)(strict default) — everyos initconfig, henceos validate/os build/os linton such a configdefineStack validation failed … objects.0.sharingModel: Invalid valueat loados validate/os compileschema step (ObjectStackSchema.safeParse) on a RAW configinvalid_valueatobjects.0.sharingModel; compile stops before any rulesaveMetaItem(Studio / REST meta / MCP) — the runtime publish gateinvalid_valueatsharingModelbygetMetadataTypeSchema('object'), which runs BEFORErunRuntimeAuthoringRules(protocol.ts, "Placed immediately after the schema check")sys_metadatasibling in the gate's context universeread/read_writefold to canonical on rehydration (applyConversionsToStoredItem, full chain incl. retired);full/publiccome back as authored — and either way the finding is produced in BOTH baseline and candidate passes and cancels in the diffos linton a RAW object-literal configloadConfigreturns the default export as authored;os lintnever parses (content/docs/deployment/cli.mdx,lint.ts);owd-legacy-read-aliasesisretiredFromLoadPath, sonormalizeStackInputleaves the alias intact (0 conversion notices)erroratobjects[0].sharingModel, hint names the canonical valuedefineStack(x, { strict: false })thenos lintcheck:doc-security-posture(packages/lint/scripts/check-doc-security-posture.mjs, wired inlint.yml)ObjectSchema.create({...})literals from docs and skills, never parsed; its own--self-testasserts the alias value is flagged — the gate ran green here: "self-test: flags the measured defect (owd-unset) and the alias value at the right page line"runRuntimeAuthoringRules/validateSecurityPosturecalled directly (both exported)objects.tier_owd.sharingModel)Re-derivation of the card's own hotcrm reading: the first row is it. On a
defineStack-authored app the alias never arrives, and the siblingsecurity-owd-unsetbranch fires on the byte-adjacent removal — the card was right about that door and wrong that it is the only door.The four yaml mentions under
packages/lint/srctriage noted are prose (object-graph.ts: "a YAML list item left empty isnull") and three tests; there is no yaml reader in the package.runRuntimeAuthoringRules's only production caller ismetadata-protocol'sevaluateRuntimeAuthoringGate, which sits behind the schema step above.Step 3 — what changed
packages/lint/src/validate-security-posture.ts— comment-only: the header table row marks the rule "UNPARSED intakes only", a new## Intakesection carries the table above, and both alias branches (sharingModel,externalSharingModel) carry a one-line pointer. Code with comments stripped is byte-identical tomain(measured: identical, 18166 chars both sides).packages/lint/src/authoring-rule-input-tier.test.ts— a describe block pinning every leg with its parsed-door control:defineStackrefuses each alias; the schema step refuses each alias;os lint's call fires with the canonical fix-it in the hint (0 conversion notices);strict: falsefires; the canonical value stays silent;getMetadataTypeSchema('object')refuses each alias; a directrunRuntimeAuthoringRulescall fires; a stored sibling folds (read,read_write) or survives (full) and cancels in the diff either way.Why not the retire branch: retiring the branches would also have to delete the
check:doc-security-postureself-test that asserts the alias verdict, and would take the only fix-it theos lintpre-flight gives a raw config carrying a retired spelling.Re-route flag for the PM (triage's escalation clause)
Branch 3 taken, so per triage the card becomes "update the coverage table's wording", cross-seat.
objectstack-ai/hotcrmis outside this session's scope; nothing was opened there. Suggested wording for hotcrm #1586's step-3 table row (the seat that owns it should confirm the row's current text):Tests and gates (all on
b8c72ac052)pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/authoring-rule-input-tier.test.ts src/validate-security-posture.test.ts src/validate-security-posture.runtime-surface.test.ts src/rule-id-barrel-exports.test.ts src/authoring-rule-wiring.test.ts—Test Files 5 passed (5),Tests 191 passed (191);os-verify-lock: VERDICT command-exit 0.pnpm --filter @objectstack/lint typecheck(tsc pluscheck:test-typecheckovertsconfig.test.json, whoseincludeissrc/**/*so the new pins are compiled) —VERDICT command-exit 0.git checkout HEAD -- path, hash equal to the HEAD blob afterwards (d616b72f…): green leg 35/35; disabling thesharingModelalias branch alone (marker on disk 1, anchor 0) — red, 4 INTAKE pins fail on the missing fix-it; disabling bothsharingModelalias branches (the retirement shape; marker 2, anchors 0) — red, 6 INTAKE pins fail. A first ablation attempt that disabled only branch 1 while the pins held rule id and path alone stayed green: the sibling "not canonical" branch emits the same id at the same path. That reading is why the pins now hold the fix-it hint.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 45 commands; all 45 ran,dispatch-gates --ran: 45 derived famil(ies) accounted for — 45 run, 0 NOT-MEASURED(43 exit 0). Two answeredPREREQUISITE NOT METexit 3 because they read a whole-repo build (check:dual-build-cjs-loads,check:type-check-debt) — NOT MEASURED here, declared to CI.pnpm check:nul-bytesOK (8099 files).pnpm --filter @objectstack/lint run check:doc-security-posturegreen.turbo ls --affectedagainst the base lists 54 packages (every consumer of@objectstack/lint). Only@objectstack/lintwas tested locally: the runtime source diff is comment-only (stripped-comment source identical tomain), so no downstream consumer can observe a behaviour change; CI runs the full affected set.skip-changesetapplies.Generated by Claude Code