You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
lint: security-owd-unset's hint recommends sharingModel 'private' on a writable external object, the shape that refuses every member edit and that security-external-owner-unmapped warns against (epic #27261) #27472
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Filing gate: ① product defect (author-time advice steering to a refused shape), a follow-up owed by landed work (#27332, PR #27422).
Reader: the epic PM (#27261) dispatches it in the epic subtree after triage; the fix is in packages/lint/src/validate-security-posture.ts (the security-owd-unset hint).
Dedupe (open AND closed): "security-owd-unset hint recommends private sharingModel external object writable" → 29 hits, none this defect (#27332 is the warning that now contradicts this hint).
reach: public entry os validate, measured by #27332's dev (report 6489300223, out-of-scope finding 4). The spike's spike_owned shape (a writable external object with no sharingModel) gets security-owd-unset (error). Its hint reads "declare sharingModel: 'private' (owner + shares; recommended), …" (validate-security-posture.ts:819). Following it yields the shape that security-external-owner-unmapped (PR #27422, 92297384) warns refuses every member edit with 403.
type: Bug
security-owd-unset's hint on an object with external.writable: true names the ways out security-external-owner-unmapped names, and does not recommend private. Severity and message are unchanged; managed objects keep today's hint.
Pin: spike_owned gets the external hint, and a managed custom object keeps the old one.
os explain security-owd-unset mentions the external case.
Clause-②: no (hint text only) · Metadata-protocol: none.
⛔ One rule: the hint for an external object points at what security-external-owner-unmapped prescribes (public_read_write plus a beforeUpdate guard, or a mapped owner column). It never recommends private there.
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Claim: PM loop round 5 2026-10-29T15:13Z
Session: session_017ihL2c5z3q4h8inrn5kwqV
Account: marchtian
Branch: claude/project-thread-6ml89f (this container can push only its designated branch; restarted from origin/main — measured platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-27472
Domain: domain:spec
Seat: epic:#27261#1 (epic PM; no domain:* seat). Maintainer order, 田騏 2026-10-29T04:21Z: 「影响你的任务进度的,你都统一接管」
File surface: packages/lint/src/** (the security-owd-unset hint for a writable external object, its explanation, tests), .changeset/** (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the hint predates external objects; #27422 made the contradiction visible
Thread-read: 6491214773
Premise: holds — on origin/main the security-owd-unset hint recommends sharingModel: 'private' for every custom object (validate-security-posture.ts, the SECURITY_OWD_UNSET arm).
Serial constraints cleared: none (#27471 touches packages/objectql and the guide only)
{
"issue": 27472,
"status": "done",
"branch": "claude/project-thread-6ml89f",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27639",
"session": "session_017ihL2c5z3q4h8inrn5kwqV",
"premise_still_valid": true,
"summary": "security-owd-unset's fix line now names security-external-owner-unmapped's two ways out on a custom object with no sharingModel that the external rule judges, instead of recommending 'private'. The two ways out are public_read_write with a beforeUpdate hook, or declaring owner_id (or created_by when there is no owner_id) and having the driver return it, then 'private'/'public_read'. Severity, message and path are unchanged, and every other object keeps today's hint byte for byte. The wording and the judgement are shared, not copied: validate-external-owner-sharing.ts now exports PUBLIC_READ_WRITE_WAY_OUT, declareOwnershipColumnWayOut(column), externalOwnershipGap(obj, readOnlyDatasources) and readOnlyExternalDatasources(stack), and both rules call them, so the external rule's own findings are byte-identical. os explain security-owd-unset gains a paragraph on the writable-external exception that points at os explain security-external-owner-unmapped. Deliberate scope reading: the swap applies where the external rule fires. An external object that already declares its ownership column, or sits on an allowWrites:false datasource, keeps the 'private' recommendation, which is correct there. This is stated in the PR's Acceptance notes. Draft PR head 4e9b79ca, assignee marchtian; CI was in_progress at report time.",
"tests": "Tests ran on code tree c4b4bd65 (HEAD~1); head 4e9b79ca adds only the changeset. (1) pnpm --filter '@objectstack/lint^...' build under os-verify-lock: VERDICT command-exit 0. (2) pnpm --filter @objectstack/lint typecheck && pnpm --filter @objectstack/lint test under lock: VERDICT command-exit 0; Test Files 177 passed (177); Tests 7604 passed | 5 skipped (7609); typecheck ran tsc --noEmit plus check:test-typecheck. (3) Targeted vitest on validate-external-owner-sharing / validate-security-posture / rule-explanations / validate-security-posture.runtime-surface: Test Files 4 passed (4), Tests 228 passed (228). The first run of (3) was red on the receiver meta-test (#5017): the new local ownershipGap was not excused. Fixed by adding it to PLUMBING. (4) Ablation, after the fix was committed: scripts/ablation-replace.mjs replaced 'const ownershipGap = externalOwnershipGap(obj, readOnlyDatasources);' with undefined. Anchor x1->x0, blob ce9dd0f8e857->f178c04309b6. Result: VERDICT command-exit 1, Tests 2 failed | 24 passed (26), the failures being 'spike_owned gets this rule's ways out…' and 'names created_by…'. Restored: blob == HEAD (ce9dd0f8e857), git diff HEAD empty. The test imports src/ relatively, so no dist rebuild was needed. NOT MEASURED, declared to CI: packages/cli/test/explain-rule-id.test.ts (unit). The CLI closure is not built here, so the file fails to load on the @objectstack/types entry before any assertion runs. It pins the managed-object hint, which is unchanged.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands at 4e9b79ca derived 58 commands. All 58 were run, with each exit captured before any pipe. --ran with 'CMD :: exit N' records: '58 derived famil(ies) accounted for — 55 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)'. 55 exited 0, including check:docs-transcript-drift ('4 declared transcript value(s) across 471 page(s) … equal what the registry derives today'), check:doc-authoring, check:nul-bytes, check:cross-package-test-inputs, check:type-check-coverage and check:type-check-debt. Three were NOT MEASURED with exit 3 (prerequisite): check-plugin-teardown-shape --self-test (a shallow clone cannot reach its pinned control commit), check:dual-build-cjs-loads and check:lean-entry-closure (no full build in this container). CI families: PR check-runs at 4e9b79ca read 18 in_progress and 3 skipped at report time. None are concluded and none are cited as green.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments), plus ToolSearch loads only. No MCP write tool was called. pull_request_read was loaded but never called; PR state was read through REST gh api.",
"api_writes": "2 relay/stamped writes: (1) fleet-write relay pr_create, request fw-20261029T155019Z-b48d06, run 40905592721 success: POST pulls -> #27639, 8179 bytes sent and 8179 stored, identical, assignees marchtian; (2) post-stamped issue comment on #27472 (this report). git push x3 to claude/project-thread-6ml89f (not REST).",
"line_budget": "6 files, +166 / -21 (187 changed lines): lint src +~75, tests +~70, changeset +16. Far under the 3000 human-merge threshold.",
"deviations": [
"Branch claude/project-thread-6ml89f instead of claude/issue-27472-*, and worktree via 'worktree add -B' (PM-set platform exception). origin/claude/project-thread-6ml89f did not exist before the first push.",
"Comment layout: the PM's stamp line comes first, then 'os-dev-report' alone on line 2. os-dev.md wants os-dev-report as the first line, and the PM's dispatch wants the stamp as the first non-empty line, so both are kept in that order.",
"The card's literal 'object with external.writable: true' is read as 'the objects security-external-owner-unmapped judges'. Writable external objects that declare their ownership column, or sit on an allowWrites:false datasource, keep the 'private' hint. Stated in the PR Acceptance notes.",
"The CLI unit pin explain-rule-id.test.ts was not run locally (CLI closure unbuilt); declared to CI."
],
"files_changed": [
".changeset/27472-owd-unset-external-hint.md",
"packages/lint/src/rule-explanations.ts",
"packages/lint/src/validate-external-owner-sharing.ts",
"packages/lint/src/validate-external-owner-sharing.test.ts",
"packages/lint/src/validate-security-posture.ts",
"packages/lint/src/validate-security-posture.test.ts"
],
"open_questions": [],
"out_of_scope_findings": []
}
PR #27639 merged through the queue as 1346052d31c78b5ce4e76a0da75865440173813e (head 78e68ad4, seat ACCEPT on the PR after an independent full Fable review and a delta review). security-owd-unset no longer recommends 'private' on an external object with no provable owner anchor: a writable binding gets security-external-owner-unmapped's two ways out, and a read-only binding gets 'public_read'. The stale lint header from #27476's review (should-fix 5) is fixed in the same PR. Carried: a read-only binding whose columnMap targets owner_id on a map-ignoring plugin driver keeps the 'private' hint; that rides with #27661.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 真挡得住的权限 | none | P③
Triage: p2 ·
domain:spec·area:access· 2026-10-29T07:57Z⛔ Not a claim.
security-owd-unsethint recommendssharingModel: 'private'on a writable external object. That is the shape access: an external object on the default sharing model refuses every member edit (403) because the ownership checks readowner_id/created_by, which no remote row carries —os validatesays nothing (epic #27261, found by the P0 spike) #27332's own warning refuses, so the author is steered from one warning into another.security-external-owner-unmappedprescribes (public_read_writeplus abeforeUpdateguard, or a mapped owner column). It never recommendsprivatethere.bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Claim: PM loop round 5 2026-10-29T15:13Z
Session:
session_017ihL2c5z3q4h8inrn5kwqVAccount:
marchtianBranch:
claude/project-thread-6ml89f(this container can push only its designated branch; restarted fromorigin/main— measured platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27472Domain:
domain:specSeat:
epic:#27261#1(epic PM; nodomain:*seat). Maintainer order, 田騏 2026-10-29T04:21Z: 「影响你的任务进度的,你都统一接管」File surface:
packages/lint/src/**(thesecurity-owd-unsethint for a writable external object, its explanation, tests),.changeset/**(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: the hint predates external objects; #27422 made the contradiction visible
Thread-read: 6491214773
Premise: holds — on
origin/mainthesecurity-owd-unsethint recommendssharingModel: 'private'for every custom object (validate-security-posture.ts, theSECURITY_OWD_UNSETarm).Serial constraints cleared: none (#27471 touches
packages/objectqland the guide only)objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
os-dev-report
{ "issue": 27472, "status": "done", "branch": "claude/project-thread-6ml89f", "pr": "https://github.com/objectstack-ai/objectstack/pull/27639", "session": "session_017ihL2c5z3q4h8inrn5kwqV", "premise_still_valid": true, "summary": "security-owd-unset's fix line now names security-external-owner-unmapped's two ways out on a custom object with no sharingModel that the external rule judges, instead of recommending 'private'. The two ways out are public_read_write with a beforeUpdate hook, or declaring owner_id (or created_by when there is no owner_id) and having the driver return it, then 'private'/'public_read'. Severity, message and path are unchanged, and every other object keeps today's hint byte for byte. The wording and the judgement are shared, not copied: validate-external-owner-sharing.ts now exports PUBLIC_READ_WRITE_WAY_OUT, declareOwnershipColumnWayOut(column), externalOwnershipGap(obj, readOnlyDatasources) and readOnlyExternalDatasources(stack), and both rules call them, so the external rule's own findings are byte-identical. os explain security-owd-unset gains a paragraph on the writable-external exception that points at os explain security-external-owner-unmapped. Deliberate scope reading: the swap applies where the external rule fires. An external object that already declares its ownership column, or sits on an allowWrites:false datasource, keeps the 'private' recommendation, which is correct there. This is stated in the PR's Acceptance notes. Draft PR head 4e9b79ca, assignee marchtian; CI was in_progress at report time.", "tests": "Tests ran on code tree c4b4bd65 (HEAD~1); head 4e9b79ca adds only the changeset. (1) pnpm --filter '@objectstack/lint^...' build under os-verify-lock: VERDICT command-exit 0. (2) pnpm --filter @objectstack/lint typecheck && pnpm --filter @objectstack/lint test under lock: VERDICT command-exit 0; Test Files 177 passed (177); Tests 7604 passed | 5 skipped (7609); typecheck ran tsc --noEmit plus check:test-typecheck. (3) Targeted vitest on validate-external-owner-sharing / validate-security-posture / rule-explanations / validate-security-posture.runtime-surface: Test Files 4 passed (4), Tests 228 passed (228). The first run of (3) was red on the receiver meta-test (#5017): the new local ownershipGap was not excused. Fixed by adding it to PLUMBING. (4) Ablation, after the fix was committed: scripts/ablation-replace.mjs replaced 'const ownershipGap = externalOwnershipGap(obj, readOnlyDatasources);' with undefined. Anchor x1->x0, blob ce9dd0f8e857->f178c04309b6. Result: VERDICT command-exit 1, Tests 2 failed | 24 passed (26), the failures being 'spike_owned gets this rule's ways out…' and 'names created_by…'. Restored: blob == HEAD (ce9dd0f8e857), git diff HEAD empty. The test imports src/ relatively, so no dist rebuild was needed. NOT MEASURED, declared to CI: packages/cli/test/explain-rule-id.test.ts (unit). The CLI closure is not built here, so the file fails to load on the @objectstack/types entry before any assertion runs. It pins the managed-object hint, which is unchanged.", "gates": "node scripts/pm/dispatch-gates.mjs --commands at 4e9b79ca derived 58 commands. All 58 were run, with each exit captured before any pipe. --ran with 'CMD :: exit N' records: '58 derived famil(ies) accounted for — 55 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)'. 55 exited 0, including check:docs-transcript-drift ('4 declared transcript value(s) across 471 page(s) … equal what the registry derives today'), check:doc-authoring, check:nul-bytes, check:cross-package-test-inputs, check:type-check-coverage and check:type-check-debt. Three were NOT MEASURED with exit 3 (prerequisite): check-plugin-teardown-shape --self-test (a shallow clone cannot reach its pinned control commit), check:dual-build-cjs-loads and check:lean-entry-closure (no full build in this container). CI families: PR check-runs at 4e9b79ca read 18 in_progress and 3 skipped at report time. None are concluded and none are cited as green.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments), plus ToolSearch loads only. No MCP write tool was called. pull_request_read was loaded but never called; PR state was read through REST gh api.", "api_writes": "2 relay/stamped writes: (1) fleet-write relay pr_create, request fw-20261029T155019Z-b48d06, run 40905592721 success: POST pulls -> #27639, 8179 bytes sent and 8179 stored, identical, assignees marchtian; (2) post-stamped issue comment on #27472 (this report). git push x3 to claude/project-thread-6ml89f (not REST).", "line_budget": "6 files, +166 / -21 (187 changed lines): lint src +~75, tests +~70, changeset +16. Far under the 3000 human-merge threshold.", "deviations": [ "Branch claude/project-thread-6ml89f instead of claude/issue-27472-*, and worktree via 'worktree add -B' (PM-set platform exception). origin/claude/project-thread-6ml89f did not exist before the first push.", "Comment layout: the PM's stamp line comes first, then 'os-dev-report' alone on line 2. os-dev.md wants os-dev-report as the first line, and the PM's dispatch wants the stamp as the first non-empty line, so both are kept in that order.", "The card's literal 'object with external.writable: true' is read as 'the objects security-external-owner-unmapped judges'. Writable external objects that declare their ownership column, or sit on an allowWrites:false datasource, keep the 'private' hint. Stated in the PR Acceptance notes.", "The CLI unit pin explain-rule-id.test.ts was not run locally (CLI closure unbuilt); declared to CI." ], "files_changed": [ ".changeset/27472-owd-unset-external-hint.md", "packages/lint/src/rule-explanations.ts", "packages/lint/src/validate-external-owner-sharing.ts", "packages/lint/src/validate-external-owner-sharing.test.ts", "packages/lint/src/validate-security-posture.ts", "packages/lint/src/validate-security-posture.test.ts" ], "open_questions": [], "out_of_scope_findings": [] }objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Landed · 2026-10-29T16:58Z
PR #27639 merged through the queue as
1346052d31c78b5ce4e76a0da75865440173813e(head78e68ad4, seat ACCEPT on the PR after an independent full Fable review and a delta review).security-owd-unsetno longer recommends'private'on an external object with no provable owner anchor: a writable binding getssecurity-external-owner-unmapped's two ways out, and a read-only binding gets'public_read'. The stale lint header from #27476's review (should-fix 5) is fixed in the same PR. Carried: a read-only binding whosecolumnMaptargetsowner_idon a map-ignoring plugin driver keeps the'private'hint; that rides with #27661.