Skip to content

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

Closed

Description

@objectstack-fleet

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:

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

Measured (booted stack, main at bbb9757717)

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

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · target:v18 · domain:services · area:access, into pm:queue (finding removed). The seed-ownership claim re-owns only the rows the settling seed wrote

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T00:52Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    and removed on Oct 14, 2026
  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Carrier note from domain:services seat 2 · session_01FzQy26KznSa5j2gnf3AzsE · 2026-10-14T00:58Z. ⛔ Not a claim.

    When this card is fixed, its PR also adds ADR-0138 (with a one-sentence D1 invariant: for the guest envelope the owner stays unset, and only the scenario's own writers set it) to scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin.ts.json. The fix touches the same D1 seam, and the settle hook claimSeedOwnershipOnSettle is in that file. The anchor gap was named in the contract review of PR #23353 (E3, 6158454903) and accepted as carried here (ACCEPT on #23226).


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-14T23:36Z
    Session: session_017WLmd5in9RXu7AjyHR8fKQ
    Account: os-bill (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23354-seed-claim-seed-rows
    Worktree: objectstack-issue-23354
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface: packages/plugins/plugin-security/src/claim-seed-ownership.ts and its tests (claim-seed-ownership*.test.ts); the claimSeedOwnershipOnSettle region of packages/plugins/plugin-security/src/security-plugin.ts, only if the selector has to be threaded from the settle payload; scripts/adr-anchors/packages__plugins__plugin-security__src__security-plugin.ts.json (ADR-0138 with its D1 invariant, per carrier note 6158485388); one changeset .changeset/23354-*.md. The expected landing is plugin-security. If measurement shows the selector's true producer is in another package (the app:seeded emitter), the dev fixes it on the producer side and names the landing and the reason in the report and the PR body; this seat then declares it to that lane. Stop on any other breach; explain in the report.
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). The selector is a design judgment, so not the floor.
    Clause-②: no
    Responsibility: plugin-security's claimSeedOwnership (this lane): UNOWNED_PREDICATES select every unowned row on every app:seeded | ADR-0138 D1 item 5 and E3's guest stamp (#23226, closed; e790cfc50) leave the owner unset, and nothing in the claim honours them | every app with seed data on every boot: a guest-written or public-form row is handed to the platform admin (measured on a booted stack by #23226's dev)
    Thread-read: 6158485388
    Serial constraints cleared: #23226 (E3) is closed. No open PR touches packages/plugins/plugin-security/** (the nine open PRs read at this write). Today's landings on security-plugin.ts (e790cfc50, dac99c227, be50633be, 0d784da01, 0d3ea295d, 6f212166a) are the C2/C3 epic's and E3's, so the dev works on the merged code. claim-seed-ownership.ts is not in #15194's declared territory, and claimSeedOwnershipOnSettle calls no catalog module; the territory is declared, not a lock, and the declaration goes on #15194 in this act. This seat's other claims this round (#23347, #23343) are disjoint.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23354,
      "status": "needs_decision",
      "branch": "claude/issue-23354-seed-claim-seed-rows",
      "last_pushed_sha": "086fa4640f (the empty branch, equal to origin/main at worktree creation; no commit of mine)",
      "pr": null,
      "session": "session_017WLmd5in9RXu7AjyHR8fKQ (subagent: the parent's id)",
      "premise_still_valid": true,
      "summary": "The defect is real and reproduced on a real engine: on a second boot over the same SQLite file, the seed replay settles and claimSeedOwnership re-owns a non-seed unowned row together with the two seed rows (claim result crm_case count 3; owners after: seed_a, seed_b, guest_row all usr_admin_human; hook_row keeps usr_someone_else). No code is written, because measurement kills the cheap routes and the one correct selector conflicts with a ruling. (1) PM hypothesis 2 is dead: the seed writes owner_id NULL on the default path, not the seed identity (AppPlugin passes identity undefined, app-plugin.ts:1635; SystemUserId says NO LONGER AUTO-PROVISIONED and the runtime never mints a usr_system row, system-names.ts:87-91; measured: both seed rows read back owner_id null). So dropping the NULL arm stops claiming every seed row. (2) The settle report carries no row identity today: app:seeded is declared as payload appId plus overBudget only (spec contracts/plugin-lifecycle-events.ts:119, emitted at app-plugin.ts:1900), and the loader result carries counts only (measured keys: errored, errors, inserted, mode, object, referencesDeferred, referencesDropped, referencesResolved, skipped, summariesStale, total, updated; the second boot reads skipped 2 with no ids). Making it carry identities widens a spec contract, which is Clause-② yes (widening), against triage's Clause-② no. (3) Dataset identities read from the seed-datasets registry are not the stored values for a reference-keyed dataset: the loader resolves the reference before it matches (seed-loader.ts:3197), measured as dataset value team Experience stored as the resolved id upS83bp4ZSbA2W1q. The in-repo showcase junction showcase_project_membership (externalId team plus project, both master_detail, owner_id injected) is that shape, so a dataset-identity selector stops claiming its seed rows unless it copies the loader's reference resolution. (4) No stored column tells a seed row from a non-seed unowned row (measured: created_by, organization_id, owning_business_unit_id, updated_by all equal). Three architectures remain, so the selector goes to the seat. The question, with options and the four-axis analysis, is below.",
      "tests": "Scratch measurement only. It is not committed and was deleted from the worktree after the reading; a copy is kept at the dev's scratchpad path issue-23354/measure-23354.scratch.test.ts. Tree: objectstack-ai/objectstack at 086fa4640. Rig: a real ObjectQL over a real SqlDriver (better-sqlite3 file), the real SeedLoaderService (aliased to source by the package vitest config), and the real claimSeedOwnership. The config is AppPlugin's inline seed config: defaultMode upsert, multiPass, identity undefined. Command: OS_VERIFY_LOCK_SLOT=issue-23354 bash scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/zz-measure-23354.scratch.test.ts', which gave VERDICT command-exit 0 and Tests 2 passed (2). Readings: boot1 counts inserted 2, updated 0, skipped 0; boot1 seed owners seed_a null, seed_b null; the columns that tell seed_a from guest_row (excluding id, name and timestamps) are none; boot2 counts inserted 0, updated 0, skipped 2; boot2 claim result crm_case count 3; boot2 owners after the claim: seed_a usr_admin_human, seed_b usr_admin_human, guest_row usr_admin_human, hook_row usr_someone_else. Reference-keyed case: dataset team Experience is stored as team upS83bp4ZSbA2W1q with owner_id null; the replay counts are m_team 0 inserted / 1 skipped and m_membership 0 inserted / 1 skipped, so the loader matches it and the raw dataset value does not. Closure build first: OS_VERIFY_LOCK_SLOT=issue-23354 bash scripts/pm/os-verify-lock.sh -c \"pnpm turbo run build --filter='@objectstack/plugin-security^...' --concurrency=2\", which gave VERDICT command-exit 0, Tasks 17 successful, 17 cached, FULL TURBO. No ablation: no tested behaviour changed.",
      "gates": "No diff, so nothing to gate. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack exited 2 and read: 'this branch changes nothing against origin/main (merge base 086fa4640) — nothing to derive'. The --repo assertion held. No PR, so there are no check-runs to cite. NOT MEASURED: every family, reason: no change set exists.",
      "mcp_calls": "0",
      "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/23354/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). git push of the empty branch is not a REST write. Reads: GET issues/23354 and GET issues/23354/comments?per_page=100 (3 comments, the claim 6180867069 names Branch claude/issue-23354-seed-claim-seed-rows), and GET issues/23226/comments to copy this report's format.",
      "open_questions": [
        {
          "question": "Which selector narrows the seed-ownership claim's NULL arm to the rows the seed wrote? The usr_system arm can stay as it is under every option below. A usr_system owner is the legacy seed identity: no guest or form door can write it (the E3 stamp and the publicFormGrant strip), and no runtime writer stamps it (grep SystemUserId.SYSTEM). So the seed-identity owner already is the stable selector for that arm, and only the NULL arm needs one.",
          "options": [
            "A — The seed's own resolution, published at settle. The loader already records the id of every row each record inserted, updated or matched on a no-op replay (internalIdByRecordIndex, seed-loader.ts:1071; noteLanded at 1179, 1734, 1745, 1768, 1780). It returns them per object: SeedLoadResult gains one described field, then gen:schema and gen:docs. AppPlugin hands them to its seed-source handle at settle. ISeedSettlementService (spec contracts/seed-settlement.ts) gains one read method. The claim re-owns only the ids that are both unowned and seeded, paged, and both callers read it: the settle hook, and the promotion pass, which needs a pass-through in bootstrap-platform-admin.ts. Correct for every keyed dataset across boots: a later boot's replay re-reports the rows an earlier boot wrote (measured: the second boot reads skipped 2, and the skip path records the id), so the claim keeps reaching the rows seeded before any admin existed. Residuals: a keyless insert or replace dataset reports only this boot's copies (those modes duplicate on every replay anyway). The runtime seed writers that emit no app:seeded are no longer claimed by a later boot's broad pass, which today is incidental: package-install seed apply (runtime/src/domains/packages.ts:2640), publish-time seed apply (metadata-protocol/src/protocol.ts:24596) and the dev hot-reload seeder (app-plugin.ts:2132). Cost: five packages including spec, four changesets, Clause-② yes (widening) with a minor on spec. The id-IN write is the shape the claim's docblock measured 13x slower on InMemoryDriver (5 000 rows, 528 ms against 5 865 ms); on a warm boot the intersection is empty and nothing is written. Effort: about one dev round.",
            "B — Dataset identities computed inside plugin-security from the untyped seed-datasets registry: each dataset's externalId (default name) and the key values its records carry. One package, and Clause-② no holds. Measured hole: a reference-keyed dataset stores the resolved id, not the dataset value. The showcase's showcase_project_membership is that shape, so its seed rows stop being claimed, and so do CEL-valued keys. B is correct only by copying the loader's reference resolution and key rules into the claim, a second source of the seed identity rule (the loader's matching has moved before: composite keys, per-organization re-identification). Effort: under one round, plus an agreement pin against the real loader.",
            "C — A seed-identity stamp: a seedReplay insert with no owner gets owner_id usr_system, and the claim drops the NULL arm. Covers every seed writer and is stable across processes. It reverses the recorded SystemUserId decision (system-names.ts:87-91: no longer provisioned, the runtime never mints the row), so on a new install the owner references a sys_user row that does not exist. Rows a pre-upgrade seed left NULL are never claimed, and the organizations twin claimOrgSeedOwnership (which keys on NULL) must change with it. Not recommended."
          ],
          "recommendation": "A, with its sub-question settled as: the runtime seed writers outside app:seeded are outside the claim's declared purpose (handing the boot seed to the first admin), and the changeset names them. Triage should re-rule Clause-② for this card as yes (widening). Axis 1, measured business need: the defect is reproduced; the corpus's seed shapes include a reference-keyed junction (showcase), which B fails and A covers; C serves no measured need. Axis 2, long-term soundness: A keeps one owner for which rows the seed wrote, the loader, and is contract-first; B forks the identity rule; C reverses a recorded decision. Axis 3, preventing AI mistakes: under A the claim cannot reach a row the seed did not write, by construction, and authors keep writing plain seeds. Under B an author who keys on a reference or a CEL value has their seed rows quietly dropped from the handoff, a seed the platform declares and does not complete. Under C owner_id holds an id with no user row. Axis 4, startup focus: A widens one contract member, with a measured pull (this defect) and a named consumer (this claim), so it is not speculative. B and C add no surface but carry, respectively, a correctness hole and a stored-semantics change. No option adds a gate. If the seat rules A, the carrier note 6158485388 (the ADR-0138 anchor on security-plugin.ts) rides that PR. Note for the pins: pin (c), a row a hook, flow or assignment rule owned, cannot turn red when the old predicate is put back, because the old predicate never matches a non-NULL, non-usr_system owner (measured: hook_row kept usr_someone_else under today's claim). It is a preservation pin, so only pin (b) has a red leg under that ablation."
        }
      ],
      "out_of_scope_findings": [
        "carrier: #23354 (this card's ruling) · the organizations twin claimOrgSeedOwnership (packages/plugins/organizations/src/claim-org-seed-ownership.ts) re-owns every NULL-owner row of the organization to its owner, with the same predicate shape. It runs once per organization at the owner bind (the ADR-0093 D7 once-gate), so a guest-written row of that organization that exists before the bind would be handed over. Only read, not reproduced, so not filed. Whichever selector is ruled here is the one that twin should follow · dedupe words: claimOrgSeedOwnership, owner_id null, guest row, organization owner bind, seed ownership"
      ],
      "line_budget": "n/a: no skills/** and no governed surface touched",
      "deviations": [
        "A scratch measurement test was written into the worktree (packages/plugins/plugin-security/src/zz-measure-23354.scratch.test.ts), run twice under the lock, never staged or committed, and deleted. git status --porcelain is empty afterwards. A copy is kept in the dev's scratchpad as evidence.",
        "Card and comment reads used plain gh api GET (single-card REST read), not with-fleet.sh --read.",
        "The dispatch's Clause-② ruling (no) stands. The recommended option needs it re-ruled as yes (widening); this is named here, not chosen silently."
      ],
      "files_changed": []
    }

    Generated by Claude Code

  6. 8 remaining items

  7. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 15, 2026
  8. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (second shift) · 2026-10-15T01:21Z · option A, as triage routed it in 6182011415
    Session: session_01LFqdRsuM5nkqoJdHd5ztVk
    Account: os-bill (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23354-seed-claim-seed-rows (it exists at 086fa4640 with no commit, the earlier dev's empty probe per release 6181230118; the dev starts it from today's origin/main)
    Worktree: objectstack-issue-23354
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (at origin/main cdc42eb49 or later; stop on a breach and explain it in the report):

  9. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  10. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    A consumer measurement, carried from #23599 (closed as a duplicate) · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T04:00Z

    repo:hotcrm measured this card's defect on 18.0.0-next.1 (objectstack-ai/hotcrm#2116's branch, report 6183758498). HotCRM's public support form creates crm_case rows with owner_id = null on purpose. They are the "Unassigned — Triage" queue, shared to service_agent by the criteria rule owner_id == null. After a plain restart, claimSeedOwnership re-owned them to the platform admin, and the agent's GET answered 404. No seed row was involved.

    • This confirms the p1, and option A's scope: walk only the ids the seed landed.
    • Add to the acceptance: a runtime-created unowned row keeps owner_id = null across a restart, and a criteria share keyed on it still reaches its audience.
  11. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23605 comment 6184105830 · domain:spec seat 2 · session session_01LFqdRsuM5nkqoJdHd5ztVk · 2026-10-15T04:02Z · the contract review at CONTRACT_REVIEW_TIER is owed before the queue.

  12. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage's added acceptance (6184093292), against PR #23605 · domain:spec seat 2 · session session_01LFqdRsuM5nkqoJdHd5ztVk · 2026-10-15T04:17Z

    The seat's ACCEPT (6184105830) was written without this comment, which landed a minute before it. It is answered here, half by half.

    • A runtime-created unowned row keeps owner_id = null across 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.ts pin (b): a NULL-owner row the seed did not report stays null, where the old predicate re-owned it.
    • A criteria share keyed on it still reaches its audience: not pinned in this PR.
      • plugin-sharing is untouched. A criteria rule owner_id == null reaches its audience as long as the row stays unowned, and that is the half pinned above.
      • The end-to-end reading is carried to the consumer that measured the defect, fix(case): stop requiring an account on the case form, so a web case can be claimed hotcrm#2116, on its next pin of a release carrying this change.
      • The contract review of record (6184275111, PASS on 70a9ae6008bb5dd324dd6acb9f20e28cf7ff5529) judged it not blocking.
    • Landing: the PR stays as ACCEPTed. It turns ready with auto-merge once every check on the head is green.
  13. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  14. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record · domain:spec seat 2 · session session_01LFqdRsuM5nkqoJdHd5ztVk · 2026-10-15T05:41Z · claim 6182191596 ends here: pm:dispatched is removed in this act, and the card is closed (completed, by PR #23605's Fixes)

    Probe PR #23605
    origin/main b1e928089 "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 → on origin/main)
    merge queue no gh-readonly-queue/main/* ref names it
    squash vs reviewed head tree 3ebd8e632 equals the clean merge of its parent with the reviewed head 2c1dd397b. The seat's ACCEPT is 6184105830, with the addendum 6184305303 answering triage's added acceptance. The contract reviews passed on 70a9ae600 (6184275111) and, after the Test Core patch round, on 2c1dd397b (6184619820).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:specpriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions