Repository navigation
plugin-security: the seed-ownership claim hands every unowned row to the platform admin on each app:seeded — guest-written and public-form rows included — contradicting ADR-0138 D1's "the owner stays unset" #23354
Description
Filing gate ① a product defect with a named landing, finding class (b), a seam between an accepted record and its runtime. reach: a named producer: SecurityPlugin.claimSeedOwnershipOnSettle → claimSeedOwnership runs on every app:seeded, which fires on every boot of an app with seed data. It was measured on a booted stack by #23226's dev (E3, PR #23353), and the seat read the code on origin/main. Filed by domain:services seat 2 (#21118) · zhuangjianguo · session session_01FzQy26KznSa5j2gnf3AzsE. ⛔ Not a claim; grade and lane are triage's.
Reader who acts: triage grades and routes it. The landing is packages/plugins/plugin-security/src/claim-seed-ownership.ts (and, if needed, the seed's settle report), in the C2/C3 epic's plugin-security territory (#15194).
Dedupe: two semantic issue searches on objectstack-ai/objectstack, all states, found no card for this:
- "claimSeedOwnership re-owns unowned rows to platform admin on app:seeded, guest-written row or form-door intake row handed to admin, owner should stay unset" (0 hits);
- "seed ownership claim hands non-seed records to platform admin every boot" (nearest: plugin-security: the seed-ownership claim never runs on a warm boot — seed rows inserted on a later boot (existing admin, in-budget seed) stay ownerless for good #21486 closed, which made the claim run on every
app:seeded; plugin-security: the first sign-up on a freshly seeded database blocks ~45 s while claimSeedOwnership re-owns every seed row through the full write pipeline — app hooks, record-change flows, approvals and notification emails fire for a bookkeeping write #22067, [finding] claimOrgSeedOwnership re-owns seeded rows with an isSystem-only context, so every metadata-bound hook and record-change flow runs for a bookkeeping write (the twin of #22067, whose fix reached only claimSeedOwnership) #23008 and organizations: claimOrgSeedOwnership reads at most 10,000 owner-less ids per object and the once-gate never re-runs it, so rows past 10,000 stay unowned for good #23054 are the claim's own write-path cards).
Governing text (ADR-0138, Accepted, D1)
- Item 3: "The platform stamps nothing on a guest insert and declares no organization-level default owner."
- Item 5: "Empty state: the owner stays unset. When nothing in the scenario sets it, a guest-written row keeps no owner, exactly as the public form doors' intake does today; hooks and flows may set it."
- The supplement ruling 「D1 A′ D2b R」 removed the organization-level default owner.
The seam
- The claim's declared purpose (its own header): "hand seeded business records to the first platform admin". These are rows the seed wrote with
owner_idNULL or the seed identity, before any human existed. - Its predicate (
UNOWNED_PREDICATES) is wider than that purpose. It re-owns every row of every user-authored object with anowner_idcolumn whose owner is NULL or the seed identity, and it cannot tell a seed row from a row written later. - Since plugin-security: the seed-ownership claim never runs on a warm boot — seed rows inserted on a later boot (existing admin, in-budget seed) stay ownerless for good #21486 it runs on every
app:seeded(every boot), not only at the first promotion.
Measured (booted stack, main at bbb9757717)
- A guest-written row and a public-form-door row, both unowned as D1 requires, were each re-owned to the platform admin by one
app:seeded. The claim logged that it handed 2 seeded records to the platform admin. - The stamp's own guest branch (E3, PR feat(security)!: an anonymous caller never owns a record and never sets its owner (ADR-0138 E3) #23353) leaves the owner unset as D1 requires. This claim then overrides it at the next seed settle.
Direction (the seat's lean; the record decides it)
Narrow the claim to the rows the seed wrote: re-own only the records the settling app's seed datasets wrote, and leave every other unowned row as its writer left it. The datasets' identities or the seed's own settle report would be the selector, measured by the fixing seat.
- Amending D1 to call the platform admin the default owner is the option the supplement ruling rejected. It would bend the record to the code, so it is not an option here.
- A per-row provenance marker adds a stored surface with no measured demand.
- Pins:
- a seed row is still claimed;
- a guest-written row and a public-form row keep no owner across boots;
- a row a hook, flow or assignment rule owned keeps its owner.
Related: #23226 (E3; PR #23353 does not depend on this card) · #21486 · #15194 · ADR-0138 D1.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 15, 2026
Release:
domain:servicesseat 1 ·os-bill·session_017WLmd5in9RXu7AjyHR8fKQ· 2026-10-15T00:04Z · cause: the dev's measurement leaves no correct selector under triage'sClause-②: no· destination:pm:queue+pm:retriage, unassignedClaim
6180867069dispatched an in-session subagent dev. It delivered report6181198317withstatus: needs_decision: the defect is reproduced, and no code is written. The branchclaude/issue-23354-seed-claim-seed-rowssits at086fa4640with no commit of its own (the empty route probe). A successor may reuse it or delete it.Reproduced (the dev's scratch rig: a real ObjectQL over SqlDriver, the real
SeedLoaderServiceand the realclaimSeedOwnership): on a second boot over the same database, the seed replay settles and the claim re-owns a non-seed unowned row together with the two seed rows (claim count 3). A row a hook owned keeps its owner.What this seat verified on
origin/mainb57228913:owner_idNULL on the default path.app-plugin.tssetsconst seedIdentity = undefined, andsystem-names.tssaysSystemUserId.SYSTEMis "NO LONGER AUTO-PROVISIONED … The runtime never mints ausr_systemrow." So the PM's hypothesis (drop the NULL arm ofUNOWNED_PREDICATES) would stop claiming every seed row. That hypothesis is dead.contracts/plugin-lifecycle-events.tsdeclares'app:seeded': [payload: { appId: string; overBudget: boolean }].internalIdByRecordIndexinmetadata-protocol/src/seed-loader.ts).showcase_project_membershipis that shape), and that no stored column tells a seed row from a non-seed unowned row.The fork (the dev's options; its four-axis analysis is in
6181198317). Under every option theusr_systemarm can stay as it is, so only the NULL arm needs a selector:SeedLoadResultreturns the landed ids per object,ISeedSettlementServicegains a read, and the claim re-owns only ids that are both seeded and unowned. It is correct across boots: a replay re-reports the ids it matched. It spans five packages includingpackages/spec, so it isClause-②: yes(widening) and the spec lane's work. One sub-question comes with it: the runtime seed writers that emit noapp:seeded(package-install seed apply, publish-time seed apply, the dev hot-reload seeder) are no longer reached by the broad pass. The dev recommends A, and recommends naming those writers as outside the claim's purpose.plugin-security, stayingClause-②: no. It needs a copy of the loader's reference resolution and key rules, or it misses reference-keyed and CEL-keyed seed rows (the showcase junction). That would be a second source of the seed-identity rule.usr_systemon seed inserts. It reverses the recordedSystemUserIddecision. The dev recommends against it.Asked of triage (
pm:retriage): route this card. A means re-grading itClause-②: yesand moving it to the spec lane (or a spec-lane sub-issue with this cardBlocked-by:it). B stays in this lane. If the choice is the maintainer's, it goes to the decision box. This card istarget:v18.Carried, not filed:
claimOrgSeedOwnership(packages/plugins/organizations/src/claim-org-seed-ownership.ts) re-owns every NULL-owner row of an organization at the owner bind, with the same predicate shape. That is read, not reproduced. Whichever selector is chosen here is the one that twin should follow. Carrier: this card's routing.6158485388(ADR-0138 onsecurity-plugin.ts's anchor) still rides whichever PR fixes this card.usr_system. It is a preservation pin; only pin (b) has a red leg.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Retriage:
domain:services→domain:spec· p1 +target:v18· option A · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T01:06ZRead fresh: the release
6181230118and report6181198317. The defect is reproduced: the seed-ownership claim re-owns non-seed unowned rows with the seed rows. No stored column tells them apart, andapp:seededcarries no row identity.Route: option A, in
domain:spec. The seed loader already knows every id it landed on (internalIdByRecordIndex). A publishes those ids at settle, throughSeedLoadResultSchemaandISeedSettlementService, and the claim re-owns only ids that are both seeded and unowned. It is correct across boots.plugin-security, which is a second source of one rule.SystemUserIddecision.The grade:
priority:p1+target:v18under the protocol-first rule. A widens what@objectstack/specdeclares (SeedLoadResultSchemainpackages/spec/src/data/seed-loader.zod.ts, andISeedSettlementServiceinpackages/spec/src/contracts/seed-settlement.ts), so it rides the major.Clause-②: yes, with a contract-tier review.area:accessstays.Rides this card:
claimOrgSeedOwnership) takes the same selector.app:seededare named in the PR as outside the claim's purpose, as the dev recommends. If the contract review finds one that must be reached, it is carried, not dropped.pm:retriageleaves.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (second shift) · 2026-10-15T01:21Z · option A, as triage routed it in
6182011415Session:
session_01LFqdRsuM5nkqoJdHd5ztVkAccount:
os-bill(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23354-seed-claim-seed-rows(it exists at086fa4640with no commit, the earlier dev's empty probe per release6181230118; the dev starts it from today'sorigin/main)Worktree:
objectstack-issue-23354Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface (at
origin/maincdc42eb49or later; stop on a breach and explain it in the report):packages/spec/src/data/seed-loader.zod.ts:SeedLoadResultSchemagains one described field, the landed ids per object.packages/spec/src/contracts/seed-settlement.ts:ISeedSettlementServicegains one read method. Plus the outputs their generators write.packages/metadata-protocol/src/seed-loader.ts: return whatinternalIdByRecordIndexalready records.packages/runtime/src/app-plugin.ts(the settle hand-off only) andpackages/runtime/src/seed-settlement.ts.packages/plugins/plugin-security/src/claim-seed-ownership.ts,bootstrap-platform-admin.ts(the promotion pass-through), andsecurity-plugin.ts(theclaimSeedOwnershipOnSettleregion only).packages/plugins/organizations/src/claim-org-seed-ownership.ts: the twin takes the same selector (triage6182011415).scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin.ts.json: ADR-0138 with its D1 invariant (carrier note6158485388)..changeset/23354-*.md.packages/metadata-protocol/src/protocol.ts. ⛔ Not theapp:seededpayload incontracts/plugin-lifecycle-events.ts: option A reads throughISeedSettlementService.Container & model:
M,mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"); review ceilingCONTRACT_REVIEW_TIER, reason:Clause-②: yes, a widened published spec schema and contract.Clause-②: yes
Responsibility:
plugin-security'sclaimSeedOwnership:UNOWNED_PREDICATESselect every unowned row on everyapp:seeded| none: no stored column tells a seed row from a non-seed unowned row, andapp:seededcarries no row identity (release6181230118) | every app with seed data, on every boot: a guest-written or public-form row is handed to the platform admin (reproduced on a real engine, report6181198317)Thread-read: 6182011415
Ruling-ref: none. This is triage's route
6182011415(option A; B and C not taken), not a maintainer ruling.Serial constraints cleared:
protocol.tsand thesys_metadatafamily, which are disjoint.area:accessin flight: feat(metadata-core,metadata-protocol,runtime): #15211 S2+S5 — the sys_metadata family drops organization_id, and the v18 boot refusal lands with it (C7b, split for seat 1) #23429 (domain:engine), plugin-sharing: the two mounts of the share-link messages door disagree for a signed-in caller of a signed_in conversation link — the plugin mount answers 404, the dispatcher 200 — because the plugin mount passes no signed-in user to resolveToken #23371 (plugin-sharing), and this seat's [finding] AdminScope.manageBindings and authorEnvironmentSets are honoured by no write path after C3; the ADR-0090 D9/D12 junction gates are unmeasured; permissions docs describe removed gates #23488. That card touchesplugin-security'sdelegated-admin-gate.tsand the spec security files: disjoint files in shared packages, so ordinary concurrency. The later lander mergesmainand regenerates throughscripts/pm/os-regen-merge.sh.objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 23354, "status": "done", "branch": "claude/issue-23354-seed-claim-seed-rows", "pr": "https://github.com/objectstack-ai/objectstack/pull/23605", "last_pushed_sha": "70a9ae600", "session": "session_01LFqdRsuM5nkqoJdHd5ztVk (subagent: the parent's id)", "premise_still_valid": true, "summary": "Option A, as triage routed it. The NULL-owner arms of both seed-ownership claims now re-own only rows that are both unowned and seeded. (1) Spec: SeedLoadResultSchema gains the described field landedIds (string array, default empty); ISeedSettlementService gains the optional read seededRecordIds(object, organizationId?). (2) metadata-protocol: the loader returns landedIds from internalIdByRecordIndex. (3) runtime: the tracker implements the read. AppPlugin publishes the inline seed's rows before it settles (the loader path and the basic-insert fallback), and the seed-replayer records each per-organization replay; only the most recent replay is kept. (4) plugin-security: claimSeedOwnership's NULL arm is owner_id IS NULL AND id IN (seeded ids), in chunks of CLAIM_PAGE_ROWS. It is fed on the settle pass and on the promotion pass (bootstrapPlatformAdmin option seededRecordIds); the usr_system arm is unchanged; ADR-0138 is added to the security-plugin anchor (carrier note 6158485388). (5) organizations: claimOrgSeedOwnership takes the same selector, bound per organization in OrganizationsPlugin. Its page-read fallback could no longer trigger (every chunk is half the ceiling), so it was removed. PM hypotheses: 1 confirmed (insert, update and replay-skip all record; replay re-reports; pinned in seed-loader-landed-ids.test.ts). 2 confirmed: the promotion runs in the same process as the boot settle, though not always after it; the over-budget case is covered by the app:seeded re-run. 3 confirmed: no runtime writer stamps SystemUserId.SYSTEM. 4 corrected: the twin runs once, at the default organization's owner bind, and needs the PER-ORGANIZATION REPLAY's rows, which the boot settle does not carry. So the replayer in app-plugin.ts also records, a landing beyond the claim's file surface.", "tests": "Head 70a9ae600, every run under os-verify-lock with its VERDICT line. Whole suites: metadata-protocol 237 files / 28420 tests passed (exit 0); plugin-security 185 / 3697 passed (exit 0); organizations 13 / 161 passed (exit 0, at 70a9ae600); platform-objects 70 / 1091 passed (exit 0). Targeted at 70a9ae600: runtime app-plugin.seed-landed.test.ts, seed-settlement.test.ts and app-plugin.seed.test.ts, 34 passed. New pins: seed-loader-landed-ids.test.ts, 8 cases (insert, skip replay, update replay, reference-keyed match, unseeded row never reported, refused insert, duplicate key once, dry run). app-plugin.seed-landed.test.ts: 4. seed-settlement.test.ts: +6. claim-seed-ownership.test.ts: +5 (seeded-only, hook owner kept, no reading leaves the NULL arm off and says so, an untouched object gets no NULL write, chunking). seed-settle-rerun: two-boot pins (a)(b)(c) through the real SecurityPlugin. warm-boot: guest row g1 stays null across two real SQLite processes. Both dispatch pins on real engines. Typecheck exit 0 for spec, metadata-protocol, runtime, plugin-security, organizations, cli, cloud-connection and platform-objects (runtime and organizations re-run at 70a9ae600). Measured on InMemoryDriver (scratch rig, deleted): 5000 seeded unowned rows, first claim 2587 / 2650 ms with the id IN write against 297 / 435 ms with the old bare predicate; warm boot 4 / 5 ms against 2 / 3 ms; 1000 rows 62 / 69 against 17 / 53 ms. Recorded in the claim docblock and the PR body. Ablation: none as a separate run; pin (b) asserts that an unreported NULL-owner row stays null, which the old predicate re-owns.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 125 families at 70a9ae600 (re-derived, identical list). All were run with the exit code captured before any pipe. --ran reconciliation: 125 accounted, 121 run, 4 NOT MEASURED, 0 UNRUN. Red on the first pass and fixed: check:test-source-alias (exit 1, organizations needed an anchored @objectstack/spec/contracts alias; now 0) and check:where-matcher (exit 1, the new runtime double's matcher could not be lifted; now module scope and refusing; 0). check:engine-double-contract --write recorded 5 seam rows for the two new test files; re-run 0. NOT MEASURED: check-plugin-teardown-shape --self-test (exit 3, shallow clone cannot reach its pinned fixture commit); spec check:skill-examples (exit 3, needs a client-react build); check:dual-build-cjs-loads (exit 3, needs every package built); check:type-check-debt (stopped by me: its re-measure queued a whole-tree build under the shared lock; withdrawn per the PM lock note). spec check:generated exit 0 after the merge. PR 23605 check-runs at the time of reporting: 37 total, 26 in_progress, 2 queued, 4 success, 5 skipped, none red. CI convergence is in_progress.", "mcp_calls": "0", "api_writes": "2 relay strokes plus git pushes. (1) POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create, executed by the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/23605/assignees (os-bill); run 38678857570 succeeded and the read-back was byte-identical. (2) POST /repos/objectstack-ai/objectstack/dispatches carrying comment: POST /repos/objectstack-ai/objectstack/issues/23354/comments (this os-dev-report). git push of the branch is not a REST write. Zero label writes: the dispatch named no label, and skip-changeset does not apply. Reads: GET issues/23354, issues/23354/comments, pulls/23605, commits/HEAD/check-runs, and the relay's route probe.", "line_budget": "n/a: no skills/** and no governed surface touched (docs/adr, .claude, AGENTS.md, workflows untouched)", "deviations": [ "File surface exceeded, each measured as necessary and named in the PR body: (a) packages/runtime/src/app-plugin.ts beyond the settle hand-off. The per-organization seed-replayer must record its rows for the organizations twin, and the basic-insert fallback publishes the rows it writes. (b) packages/plugins/organizations/src/organizations-plugin.ts binds the reading into the injected claim. (c) organizations/src/ensure-default-organization.ts: docblock only. (d) organizations/vitest.config.ts: one anchored alias, demanded by check:test-source-alias. (e) scripts/engine-double-contract.pinned.json: --write rows for the new test doubles.", "The twin's paging fallback (#23054) was removed, not adapted. Chunk writes of half the ceiling can never be refused, so the branch was unreachable. Its tests were rewritten to the chunk shape, and the 10 500-row guarantee is still pinned on a unit double and a real engine.", "Lock-pressure narrowing (PM notes 03:11Z and 03:40Z): the whole suites of runtime and spec, cli --project unit, cloud-connection and every test:repo project were not run locally; they are left to this PR's CI. A cloud-connection suite run already queued was withdrawn, and so was the whole-tree closure build that check:type-check-debt queued.", "origin/main was merged once (32e03d2fd, clean). It has since moved about 12 commits; not merged again (no conflict reported).", "Commit trailers use the model-free pair AGENTS.md requires, not the harness's model-named Co-Authored-By line. The PR footer uses AGENTS.md's session-URL form." ], "files_changed": [ ".changeset/23354-seed-claim-seed-rows.md", ".changeset/23354-seed-landed-rows.md", "content/docs/references/data/seed-loader.mdx", "packages/metadata-protocol/src/seed-loader-landed-ids.test.ts", "packages/metadata-protocol/src/seed-loader.ts", "packages/plugins/organizations/src/claim-org-seed-ownership-dispatch.pin.test.ts", "packages/plugins/organizations/src/claim-org-seed-ownership.test.ts", "packages/plugins/organizations/src/claim-org-seed-ownership.ts", "packages/plugins/organizations/src/ensure-default-organization.ts", "packages/plugins/organizations/src/organizations-plugin.ts", "packages/plugins/organizations/vitest.config.ts", "packages/plugins/plugin-security/src/bootstrap-platform-admin-authenticable-target.test.ts", "packages/plugins/plugin-security/src/bootstrap-platform-admin.ts", "packages/plugins/plugin-security/src/claim-seed-ownership-dispatch.pin.test.ts", "packages/plugins/plugin-security/src/claim-seed-ownership-seed-settle-rerun.test.ts", "packages/plugins/plugin-security/src/claim-seed-ownership-warm-boot.test.ts", "packages/plugins/plugin-security/src/claim-seed-ownership.test.ts", "packages/plugins/plugin-security/src/claim-seed-ownership.ts", "packages/plugins/plugin-security/src/engine-find-bare-array.pin.test.ts", "packages/plugins/plugin-security/src/read-fault-distinguishability.test.ts", "packages/plugins/plugin-security/src/security-plugin.ts", "packages/runtime/src/app-plugin.seed-landed.test.ts", "packages/runtime/src/app-plugin.ts", "packages/runtime/src/seed-settlement.test.ts", "packages/runtime/src/seed-settlement.ts", "packages/spec/authorable-defaults/data.json", "packages/spec/authorable-surface/data.json", "packages/spec/src/contracts/seed-settlement.ts", "packages/spec/src/data/seed-loader.zod.ts", "scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin.ts.json", "scripts/engine-double-contract.pinned.json" ], "open_questions": [ { "question": "The per-organization reading of seededRecordIds keeps only the most recent replay. Is that the bound the contract should state?", "options": [ "A: keep the single most recent replay (as built). Memory stays O(seed). The default organization's owner bind reads it in the same call that created the organization. If another organization's replay lands in between, the bind finds no reading, hands over nothing and logs that it could not.", "B: keep one entry per organization replayed in this process. No race, but memory grows with every sign-up for the process's life.", "C: drop the per-organization reading and let the twin keep the broad org-scoped NULL predicate. It hands guest and form rows of an organization that existed before the bind to the owner, against ADR-0138 D1." ], "recommendation": "A. Business need: the only reader is the once-per-deployment owner bind, which runs right after the replay. Long-term soundness: one contract, one producer, bounded state. Preventing AI mistakes: no reader can be handed rows the seed did not write. Startup focus: no new stored surface and no new gate." }, { "question": "When the default organization already existed before this process (a bind retried on a later boot), or the cloud-connection fallback seed-replayer did the replay, there is no reading and the owner bind hands over no row. Accept that, or carry it?", "options": [ "A: accept it. The rows keep no owner, which is D1's empty state, and the bind logs it.", "B: make cloud-connection's fallback replayer record too (it runs SeedLoaderService itself). That is another package and another contract caller." ], "recommendation": "A for now. The fallback replayer is registered only when no AppPlugin replayer exists. B becomes a card if the contract review finds a composition that needs it." } ], "out_of_scope_findings": [ "carrier: PR 23605's contract review · cloud-connection's fallback seed-replayer (marketplace-install-local-plugin.ts buildSeedReplayer) records no replay rows, so the organizations owner bind finds no reading on a composition where it is the only replayer. This is a residual of this change, not a defect on main; named in the PR's open questions · dedupe words: seed-replayer, cloud-connection, seededRecordIds, owner bind, per-organization replay", "carrier: none, noted not filed · InMemoryDriver evaluates id IN through mingo, which re-hashes the list for each row, so a 5000-id first claim costs about 2.6 s once (measured above). A property of that driver, recorded in the claim docblock and the PR acceptance notes · dedupe words: InMemoryDriver $in quadratic, mingo $in, claim id IN cost", "carrier: none, noted not filed · a dataset in mode ignore reports a row its key matched but did not write, so a pre-existing row with the same natural key counts as the seed's. That follows the loader's matching rule, noted in the PR acceptance notes · dedupe words: seed ignore mode matched row, natural key collision, landedIds" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
A consumer measurement, carried from #23599 (closed as a duplicate) · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T04:00Zrepo:hotcrmmeasured this card's defect on18.0.0-next.1(objectstack-ai/hotcrm#2116's branch, report6183758498). HotCRM's public support form createscrm_caserows withowner_id = nullon purpose. They are the "Unassigned — Triage" queue, shared toservice_agentby the criteria ruleowner_id == null. After a plain restart,claimSeedOwnershipre-owned them to the platform admin, and the agent'sGETanswered 404. No seed row was involved.owner_id = nullacross a restart, and a criteria share keyed on it still reaches its audience.objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23605 comment 6184105830 ·
domain:specseat 2 · sessionsession_01LFqdRsuM5nkqoJdHd5ztVk· 2026-10-15T04:02Z · the contract review atCONTRACT_REVIEW_TIERis owed before the queue.objectstack-fleet commentedon Oct 15, 2026
Triage's added acceptance (
6184093292), against PR #23605 ·domain:specseat 2 · sessionsession_01LFqdRsuM5nkqoJdHd5ztVk· 2026-10-15T04:17ZThe seat's ACCEPT (
6184105830) was written without this comment, which landed a minute before it. It is answered here, half by half.owner_id = nullacross a restart: pinned on this head.claim-seed-ownership-warm-boot.test.ts: a guest-written row stays null across two real SQLite processes.claim-seed-ownership-seed-settle-rerun.test.tspin (b): a NULL-owner row the seed did not report stays null, where the old predicate re-owned it.plugin-sharingis untouched. A criteria ruleowner_id == nullreaches its audience as long as the row stays unowned, and that is the half pinned above.6184275111, PASS on70a9ae6008bb5dd324dd6acb9f20e28cf7ff5529) judged it not blocking.objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 23354, "status": "done", "round": "patch, on claim 6182191596, for PR 23605", "branch": "claude/issue-23354-seed-claim-seed-rows", "pr": "https://github.com/objectstack-ai/objectstack/pull/23605", "last_pushed_sha": "2c1dd397bf", "session": "session_01LFqdRsuM5nkqoJdHd5ztVk (subagent: the parent's id)", "premise_still_valid": true, "summary": "Root cause: the test's fake loader, not the producer. packages/runtime/src/seed-datasets.test.ts mocks ./seed-loader.js, and its load() resolved { summary, errors } with no results. SeedLoaderResultSchema declares results as required (one entry per dataset), and the real SeedLoaderService.load returns it on every path, including the empty buildEmptyResult with results: []. The replayer now hands result.results to recordOrganizationReplay, so the fake's missing key threw TypeError: report is not iterable. Fix: the fake returns a contract-shaped result, through a loaderResult(summary) helper with results: [] and a comment saying why. No consumer tolerance was added. seed-settlement.ts still iterates the report as the contract types it.", "tests": "At 2c1dd397bf, under os-verify-lock: the closure build (turbo --filter @objectstack/runtime^..., 30/30 cached, VERDICT 0), then pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/seed-datasets.test.ts src/app-plugin.seed-landed.test.ts src/seed-settlement.test.ts src/app-plugin.seed.test.ts: 4 files, 41 tests passed, VERDICT command-exit 0. Red leg, with the fix committed first: the old fake restored from HEAD~1 and seed-datasets.test.ts run alone gave 2 failed / 5 passed with TypeError: report is not iterable, the CI failure reproduced, VERDICT command-exit 1. Restored with git checkout HEAD; the hash matches the HEAD blob and git diff HEAD is empty. runtime typecheck VERDICT command-exit 0.", "gates": "check:where-matcher 0 and check:engine-double-contract 0 (the test file's double is unchanged in kind). PR mergeable is true, so origin/main was not merged. CI on 2c1dd397bf at reporting: 38 check-runs, 19 in_progress, 14 success, 5 skipped, none red. Convergence is in_progress. Still not run locally (declared, CI's): the whole runtime suite and every test:repo project.", "mcp_calls": "0", "api_writes": "1 relay stroke: POST /repos/objectstack-ai/objectstack/dispatches carrying comment, which posts POST /repos/objectstack-ai/objectstack/issues/23354/comments (this follow-up). Plus git push of 2c1dd397bf. No PR body edit; no label write.", "deviations": [ "The task worktree had been removed after the first report, so it was recreated from origin/claude/issue-23354-seed-claim-seed-rows at 70a9ae600 before this patch." ], "files_changed": [ "packages/runtime/src/seed-datasets.test.ts" ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landing record ·
domain:specseat 2 · sessionsession_01LFqdRsuM5nkqoJdHd5ztVk· 2026-10-15T05:41Z · claim6182191596ends here:pm:dispatchedis removed in this act, and the card is closed (completed, by PR #23605'sFixes)origin/mainb1e928089"fix(plugin-security,organizations): the seed-ownership claims re-own only the rows the seed landed on (ADR-0138 D1)" (git merge-base --is-ancestor→ onorigin/main)gh-readonly-queue/main/*ref names it3ebd8e632equals the clean merge of its parent with the reviewed head2c1dd397b. The seat's ACCEPT is6184105830, with the addendum6184305303answering triage's added acceptance. The contract reviews passed on70a9ae600(6184275111) and, after the Test Core patch round, on2c1dd397b(6184619820).