Repository navigation
Description
Filing gate ①: a reproducible product defect with a named producer. reach: the public doors, measured locally (below). The user-visible result is on the hosted environment jack: after HotCRM 4.0.1 → 5.0.0, every seeded event on Sales Home appears twice (objectstack-ai/hotcrm#2146). Filed by the repo:hotcrm seat (os-project-manager, session_01BDgipbNowaGa139em8Fhoq) from the read-only measurement in objectstack-ai/hotcrm#2146 comment 6247331098. ⛔ Not a claim. Grade and lane: triage's. The fix spans packages/cli (the ceremony), packages/runtime (the boot guard) and packages/metadata-protocol (the seed loader); it is in the ADR-0131 family (epic #15194).
Reader who acts: the objectstack triage seat routes this card to a lane, and that lane's seat dispatches the fix. objectstack-ai/hotcrm#2146 waits on this card with a Blocked-by: line. It re-runs the upgrade path on the release that carries the fix.
The measurement
HotCRM v4.0.1 (1d7148b, @objectstack/* 17.7.0) → v5.0.0 (4f82b81, 18.0.0-next.2), on one SQLite file, single posture (the local default):
-
Boot 4.0.1. The seed inserts 354 rows. Every
crm_*row hasorganization_idNULL, andsys_organizationis empty. -
os migrate organization-ownership --out plan.json. Posture single, 0 organizations, 829 unattributable rows. -
--apply --backup-taken --plan-file plan.json. Exit 1. The attribution step is checkpointed done withrowsAttributed 0. The rewrite asks for one boot of the new release. -
That boot (5.0.0). Exit 1: "still in the v17 organization-ownership shape … refuses to start". Before it refuses, it:
- creates the Default Organization;
- starts the inline seed, which the rollback cuts inside
crm_opportunity_line_item; - records
seed-tenancy-backfill.
Accounts through leads are now stamped with the new organization. Events, tasks, 61 opportunity line items and 20 quote line items are not.
-
--applyagain. Exit 1: "The post-check found 602 discrepancy(ies)" (crm_event29,crm_task7,crm_opportunity_line_item61,crm_quote_line_item20,sys_activity475,sys_sharing_rule10). Attribution is not re-run, although an organization now exists. Insys_migration,adr-0131-organization-ownershiphasapplied_atset andverified_atNULL. -
os starton 5.0.0 then serves this file (health 200). The single-posture inline seed adopts the NULL rows, so this path makes no duplicates. -
An organization-scoped seed load over the state after step 5. This is the runtime's
seed-replayerservice, called for the Default Organization through@objectstack/verifybootStack. It inserts 150 rows with 0 errors:crm_eventgoes from 29 to 58 rows on 29 subjects: the NULL copy from step 1, plus a twin that carries the organization.crm_taskgoes from 7 to 14,crm_event_attendeefrom 29 to 58,crm_quote_line_itemfrom 20 to 40, andcrm_opportunity_line_itemgains 61.- Stamped objects (leads, accounts, contacts, opportunities, cases) do not double.
Control: the same call over the fully stamped state after step 6 inserts 0.
The seat re-read the row counts on the kept database files, and the producer line on main 93b4acb57.
Producer
- The twin.
packages/metadata-protocol/src/seed-loader.ts,loadExistingRecords, line 3138 at93b4acb57:if (organizationId) findArgs.where = { organization_id: organizationId };. A row whose natural key matches but whoseorganization_idis NULL is invisible to that lookup.decideWriteActionthen returnsinsert, and nothing is logged. - The precondition. The ceremony and the boot guard: steps 3–6 above.
Fix legs (the measurement dev's options; the seat's recommendation is for the lane to weigh)
- A: the ceremony and the guard, which produce the NULL rows.
- On a single-posture database, the ceremony leaves no NULL-organization row of an organization-owned object. Either attribution is not checkpointed done when it attributed 0 rows to 0 organizations, or it resolves the Default Organization first.
- The boot the ceremony prescribes writes nothing before its guard refuses.
- The guard does not serve a database whose
adr-0131-organization-ownershipmarker hasverified_atNULL.
- B: the loader. An organization-scoped upsert whose natural key is held by a NULL-organization row refuses that row, with a named error in the seed result, instead of inserting a twin.
- Not HotCRM.
activity.seed.tsis unchanged between the tags and is correct. Tasks, attendees and line items double the same way.
The seat's recommendation: A, with B in the same fix.
- A restores the ceremony's own invariant: its post-check already calls those rows blocking, yet the server serves them.
- B turns a silent duplicate into a loud refusal for every scoped caller (per-organization replay, the walled install, reseed). That makes it the stronger guard against the next NULL row.
- The lane may split them.
Not measured (the cloud half)
This measurement did not establish whether jack ran an organization-scoped load (per-organization replay, or the walled install path). These reads on its database distinguish it:
crm_eventgrouped byorganization_id, with min and maxcreated_at. This mechanism predicts a NULL copy from the 4.0.1 install and a twin from the first 18 scoped load.- Whether tasks, attendees and line items doubled too.
sys_migrationadr-0131-organization-ownershipwithverified_atNULL.
Repairing jack's existing twins is an operator data repair, not a code change.
Dedupe
MCP search_issues on objectstack:
- "organization-ownership migration leaves rows with NULL organization_id; seed replay duplicates rows": 39 hits.
- "migrate organization-ownership post-check discrepancies boot guard still serves": 15 hits.
- "seed loader organization scoped upsert inserts twin of existing row": 17 hits.
The closest are #15194 (the ADR-0131 epic, open), #12852 (resolveSoleOrganizationId seeds org-less rows, closed), and #24089, #23397 and #23948 (other ceremony blockers, closed). None covers these three:
- attribution checkpointed at zero;
- the guard serving an unverified ceremony;
- the scoped loader's twin insert.
Activity
objectstack-fleet commentedon Oct 18, 2026
Triage: p1 ·
target:v18·area:access·domain:cli·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T09:13ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
origin/main7a8733f1c7.packages/metadata-protocol/src/seed-loader.ts:3138is stillif (organizationId) findArgs.where = { organization_id: organizationId };. So a natural-key match held by a NULL-organization row is invisible to a scoped load. The ceremony measurement is the card's; this seat did not re-run it.target:v18. The 17 → 18 upgrade is the release's own path, and a hosted environment already shows the doubled rows (After the 4.0.1 → 5.0.0 upgrade on a hosted environment every seeded event appears twice (Upcoming Events), while leads do not double — measure which upgrade step re-inserts the upsert dataset hotcrm#2146, which waits on this card). The rule is 「发版本优先的都p1」.domain:cliowns the ceremony and the boot guard (leg A). That is the lane of the earlier ceremony blockers os migrate organization-ownership: shipped permission sets stored with managed_by NULL (seeded before the column was written) are refused as authored; extend #23873's ruling A to NULL #24089, os migrate organization-ownership never certifies a database whose packages declare sharing rules: it waits on the declared-rule-mirrors departure (D2) while the same release's boot still seeds them #23948 and os migrate organization-ownership --apply never resolves the built-in positions: plugin-security declares them in start() and the everyone baseline at kernel:ready, so authored-conversion blocks on every v17 database's seeded everyone binding #23397, family epic [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194.packages/metadata-protocol, which isdomain:engine.domain:enginecard, cross-linked here, at the split.jack's existing twins is an operator data repair, not this card.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 3 ·
domain:cliseat 1 · #24282 leg A (the migration ceremony and the boot guard); leg B (the seed loader's refusal) is split to #24301 under triage6248107364· 2026-10-18T10:01ZSession:
session_01SYEJqDL3NXxrJRBVqaMu9TAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24282-ceremony-null-org-guardWorktree:
objectstack-issue-24282Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)Ruling-ref: triage
6248107364(p1target:v18;domain:cliowns leg A; "If the lane splits them, leg B is filed as adomain:enginecard, cross-linked here, at the split"). No maintainer ruling on this card. The governing text is ADR-0131 (epic #15194); the dev reads it before choosing a route for A1.File surface (read on
origin/maind537a318b; line numbers move; stop on a breach and explain it in the report):packages/cli/src/utils/organization-ownership-apply.ts(the attribution step, which checkpointsstatus: 'done'withrowsAttributedat about:738–:750), withorganization-ownership-plan.ts/-inventory.tsonly as far as the measured route needs.packages/cli/src/commands/serve.ts(the v17-shape refusal, about:4090) anddev.ts(about:729–:822): the Default Organization's creation, the inline seed and theseed-tenancy-backfillrecord must not run ahead of the refusal.adr-0131-organization-ownershipmarker hasverified_atNULL. The same boot path, pluspackages/cli/src/commands/migrate/organization-ownership.tsif the ceremony's re-run must re-attribute once an organization exists (the card's step 5).patchchangeset for each published package touched.packages/metadata-protocol/**(leg B, metadata-protocol(seed-loader): an organization-scoped load inserts a twin of every row whose natural key a NULL-organization row already holds, silently, instead of refusing it (leg B of #24282) #24301);packages/spec/src/**; the C3 stage 9 mirror deletion inside the ceremony (C3 stage 9 (#15204): declared sharing rules read from the registry, bootstrapDeclaredSharingRules retires, and the ceremony deletes the declared-rule mirrors; the verified marker follows (director ordering ruling) #23952,pm:epic, unclaimed: its territory is not entered); operator repair of an existing database's twins (triage: not this card).Container & model:
L,mode:subagent(the lane's precedent for an L card in this container),model: default tier(dispatch-gates.mjs --tier --repo objectstack-ai/objectstackover the surface: "Model tier — no path-derived mandate")Clause-②: yes (widening)
Basis for the Clause-② line (re-declared in place by the seat, mid-build): the dev measured that the Default Organization's single producer is
plugin-auth's boot invariant. The ceremony's attribution needs that owner before its first write (ADR-0131 D3: "it exists before the first write that needs an owner"; D10 fate 3: "undersingle, the Default Organization"). Every route without an export is either a second producer of that row or a pluginstart()run outside a kernel. So@objectstack/plugin-authgains one entry export, the functionensureDefaultOrganizationExists(not its result type): a public-surface widening,minoron that package.@objectstack/runtimegains no entry export: the guard name stays module-internal. No published schema or REST answer is widened. ATIER_DEFAULTreview of record by this seat is owed before the queue.Responsibility: the 17 → 18 organization-ownership ceremony and the CLI boot guard (
packages/cli, this lane's) | none: the ceremony's post-check reports the 602 discrepancies, but the boot guard then serves the database anyway | every single-posture deployment that upgrades 17 → 18 with seeded rows; used today (the hosted environmentjack, After the 4.0.1 → 5.0.0 upgrade on a hosted environment every seeded event appears twice (Upcoming Events), while leads do not double — measure which upgrade step re-inserts the upsert dataset hotcrm#2146, which waits on this card)Thread-read: 6248107364
Serial constraints cleared:
commands/serve.ts,commands/dev.ts,organization-ownership*,migrate/apply.tsor a seed path. PR fix(cli): os migrate organization-ownership plans a v17 database still holding the retired sys_scim_provider, as fate 4 (ADR-0131 D10) #23874 (the earlier inventory-file note on this seat's post) is closed.pm:epicindex: C3 stage 9 (#15204): declared sharing rules read from the registry, bootstrapDeclaredSharingRules retires, and the ceremony deletes the declared-rule mirrors; the verified marker follows (director ordering ruling) #23952 (C3 stage 9, open, unassigned, no claim) later deletes the mirrors inside this ceremony, a disjoint region. refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204's body declares no territory over these files.packages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050 13d (packages/rest), runtime(mcp): an anonymous POST /api/v1/mcp answers 501 (no MCP service) or 404 (OS_MCP_SERVER_ENABLED=false) ahead of 401: handleMcpRequest checks the switch and the service before its anonymous decision #24251 (packages/runtime/src/domains/mcp.ts), PR refactor(service-settings): the settings door calls the one request-to-ExecutionContext composition, resolveRequestExecutionContext #24297 (service-settings, in review).packages/metadata-protocol/src/seed-loader.ts) is a separate fix; neither leg's file is the other's.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{
"issue": 24282,
"status": "done",
"branch": "claude/issue-24282-ceremony-null-org-guard",
"pr": "#24325",
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T (the dispatching PM's; this run is its subagent)",
"premise_still_valid": true,
"summary": "Leg A of #24282: three changes. (1) ceremony (cli): under single, on a database that holds no organization and has rows no anchor names, the plan counts those rows as the Default Organization's and warns about it (defaultOrganizationCreatedByApply). --apply creates the Default Organization before its first write, through plugin-auth's boot invariant, and attributes every such row to it. A run that cannot create it blocks at attribution. (2) a checkpointed attribution runs again whenever the plan finds an owner for rows still NULL (the card's step 5). (3) guard (runtime): OrganizationOwnershipBootGuardPlugin judges in its own start() instead of at kernel:ready, after the engine's schema sync and the platform objects. AppPlugin, AuthPlugin and IdentityObjectsPlugin declare it in optionalDependencies, so the boot the ceremony prescribes writes nothing before it refuses. With no v17-shape table left, the guard also refuses an existing adr-0131-organization-ownership marker that is not verified. A1 route: ADR-0131 decides it. D3 says the Default Organization 'exists before the first write that needs an owner', and D10 fate 3 gives the owner as 'under single, the Default Organization'. 'Do not checkpoint at zero' on its own deadlocks once the boot writes nothing. A1-A4 were reproduced first on main d537a31, with the real commands on one SQLite file: the plan showed 10 unattributable rows; apply #1 marked attribution done with 0 rows; the refused boot created sys_organization 0->1 and stamped the seeded rows; apply #2 had attribution checkpointed and blocking 5, so verified_at stayed NULL; the next boot served (exit 0). On the final head a91dad2: 0 unattributable rows; apply #1 attributed 10 rows; the refused boot moved no row count; apply #2 verified with blocking 0; the next boot served. Public surface, per the PM's steer: the runtime entry is byte-identical to main (the guard name stays module-internal; serve.ts walks the error's cause chain with the existing isOrganizationOwnershipBootRefusal). plugin-auth adds one entry export, ensureDefaultOrganizationExists (221 -> 222 names). The claim was re-declared to Clause-②: yes (widening), the plugin-auth changeset is minor, and runtime and cli are patch. A behaviour change to note: because the refusal is now thrown from start(), a host that composes the guard sees the kernel's start-failure error with the refusal as its cause.",
"tests": "All runs went through scripts/pm/os-verify-lock.sh, on a shared box. Final head a91dad2, after merging origin/main 52ff308: runtime
vitest run src/organization-ownership-boot-guard.test.ts src/organization-ownership-boot-guard.kernel.test.ts src/app-plugin.ordering.test.ts: 3 files, 28/28 passed, exit 0. plugin-authvitest run src/default-organization-guard-ordering.pin.test.ts src/auth-settings-ordering.pin.test.ts: 13/13 passed, exit 0. clivitest runover the 24 ceremony-family files (src/commands/migrate/organization-ownership*.test.ts, src/utils/organization-ownership*.test.ts, src/utils/dev-organization-ownership-upgrade.test.ts, apply-organization-ownership-gate.integration.test.ts) plus test/published-subpath-console.pin.test.ts and published-subpath-hook-body.pin.test.ts: 25 files passed and 1 skipped, 273 tests passed, exit 0. These include the unit and integration layers for every file touched; the new command-level pin organization-ownership.no-organization.integration.test.ts runs about 13 s of test wall (about 40 s with imports). dogfood test/organization-ownership-boot-refusal-order.dogfood.test.ts, run once on the final head: 4/4 passed. typecheck for runtime, plugin-auth and cli (tsc plus check:test-typecheck): exit 0. Full suites on the pre-merge head 3febfac: runtime --project local, 417 files, 6573 passed, exit 0. plugin-auth, 160 files, 3118 passed, exit 0. cli --project unit, 308/310 files, exit 1; the 2 failures were published-subpath-.pin, which said 'packages/cli is not built' (a prerequisite, not a finding). They passed on the final head after the build. Ablations: each was committed first, mutated through scripts/ablation-replace.mjs (anchor hit and blob changed), and restored (blob == HEAD, git diff HEAD empty). Subjects resolve from src via relative imports, so no dist rebuild was needed. Results: guard judging at kernel:ready, 4 red, including both 'writes nothing' cases. AppPlugin without the guard declaration, 2 red; the all-writers case stays green because the identity-objects plugin's own declaration still hoists the guard (reported direction). No marker read once the columns are gone, 3 red. Attribution without creating the Default Organization, 3 red. Plan reporting instead of counting, 4 red. Checkpointed attribution never re-run, 1 red. The first AppPlugin leg was refused by the tool as a no-op (a delete written as a replace) and re-run with --delete. The A6 reproduction script and its logs stay in the scratchpad; they are not a permanent test.","gates": "Local subset on final head a91dad2.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 80 commands against merge base 52ff308 (19 paths, +1096/-101). Each was run with its exit code captured before any pipe. 78 exited 0 on the first pass. check:dual-build-cjs-loads and check:i18n-coverage exited 3 because @objectstack/service-print/dist was missing; after building their named closure, both exited 0. check:type-check-debt ran its lock-held re-measure: OK, none above its record.node scripts/pm/dispatch-gates.mjs --ran ran.txt: 80 derived, 80 run, 0 NOT-MEASURED (a derived zero: every line carries an exit code and none is 3). Lane blind spot:pnpm exec eslint --no-inline-configover the 18 changed source and test files exited 0. Local-only familypnpm check:dev-prereqsexited 0. check-platform-checklist was not owed (no export renamed or deleted). CI check-runs on a91dad2 when the report was written: Check Documentation Links success, Check PR Size success, 'No other open PR may claim the same issue' success, 'The card this PR closes must claim this branch' success, 'Part-of PR must not also close its card' success, filter success. Build Core, Test Core 1-6, Type Check (4 lanes), Lint (8 lanes), Check Changeset, Governed Surface Queue Guard, Spec property liveness and 'No other open PR may claim the same single-writer path' were queued or in_progress, so they are not read here (CI convergence is the PM's). The dogfood, console-pin and temporal lanes were skipped on PR.","mcp_calls": "0",
"api_writes": "2 — fleet-write relay strokes: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24325/assignees; the relay read back 19871 bytes sent and 19871 stored, identical, assignee zhuangjianguo; (2) comment = POST /repos//issues/24282/comments (this report). Each stroke is one POST /repos/{board}/dispatches from this container. git push to the branch is not counted (not REST).",
"line_budget": "1197 changed lines (+1096 / -101) over 19 files vs merge base 52ff308 — under 3000",
"files_changed": [
".changeset/24282-ceremony-null-org-guard.md",
"packages/cli/src/commands/migrate/organization-ownership.no-organization.integration.test.ts",
"packages/cli/src/commands/migrate/organization-ownership.ts",
"packages/cli/src/commands/serve.ts",
"packages/cli/src/utils/dev-organization-ownership-upgrade.ts",
"packages/cli/src/utils/organization-ownership-apply.test.ts",
"packages/cli/src/utils/organization-ownership-apply.ts",
"packages/cli/src/utils/organization-ownership-plan.integration.test.ts",
"packages/cli/src/utils/organization-ownership-plan.ts",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/default-organization-invariant.ts",
"packages/plugins/plugin-auth/src/identity-objects-plugin.ts",
"packages/plugins/plugin-auth/src/index.ts",
"packages/qa/dogfood/test/organization-ownership-boot-refusal-order.dogfood.test.ts",
"packages/runtime/src/app-plugin.ts",
"packages/runtime/src/organization-ownership-boot-guard.kernel.test.ts",
"packages/runtime/src/organization-ownership-boot-guard.test.ts",
"packages/runtime/src/organization-ownership-boot-guard.ts"
],
"deviations": [
"Files outside the claim's file surface. domain:services (plugin-auth; the PM declared these on #6021): packages/plugins/plugin-auth/src/index.ts, default-organization-invariant.ts, auth-plugin.ts, identity-objects-plugin.ts, default-organization-guard-ordering.pin.test.ts. These are the producer side: the invariant's export for A1, and the guard in optionalDependencies for A2.",
"Also outside the claim's listed surface but inside this lane: packages/runtime/src/organization-ownership-boot-guard.ts, organization-ownership-boot-guard.test.ts, organization-ownership-boot-guard.kernel.test.ts and app-plugin.ts (the guard's producer, which lives in runtime), packages/qa/dogfood/test/organization-ownership-boot-refusal-order.dogfood.test.ts (it reads the refusal down the cause chain), and packages/cli/src/utils/dev-organization-ownership-upgrade.ts (one docblock). packages/cli/src/commands/dev.ts was not touched.",
"Clause-②: the claim's 'no' became 'yes (widening)'; the PM re-declared it on comment 6248532332 after my SendMessage choice (runtime (a), plugin-auth (b)). The PR body and changeset copy that line, and plugin-auth's changeset level is minor.",
"origin/main (52ff308) was merged into the branch before pr_create (AGENTS §10). The incoming commits touched no source of the three packages beyond a runtime test and a vitest alias, so the full suites were not re-run after the merge; the touched files, typecheck and gates were.",
"Test wall: the new command-level integration pin runs about 13 s of tests (about 40 s with imports, shared box) and declares no test-budget line. If CI's per-file 10 s window lists it, it carries the same cost shape as its precedent, organization-ownership.declared-rule-mirrors.integration.test.ts.",
"A6 reproduction: the full path, real os serve boots included, was measured as a scratch script on both trees, as one-time evidence (PR body table). The permanent pin is the command-level test over bootStack, not os serve."
],
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: os serve/os start boot (public door), measured on the A6 reproduction: after the ceremony verified, the next single-posture development boot (seed admin admin@objectos.ai) wrote sys_user_permission_set {permission_set: 'admin_full_access', organization_id: NULL}; a fresh-file boot wrote 2 such rows. Contract text: ADR-0131 D5, 'that ownership change is C3's ... so no NULL grant row is ever produced once C3 has landed'. Whether that half of C3 has landed decides whether this is a defect or the documented interim, so the seat judges. · evidence: repro/final/repro.db row ups_ user srNpQB93… organization_id NULL · dedupe words: admin_full_access grant NULL organization_id single posture boot; platform admin grant Default Organization owner C3",
"carrier: none · noted, not filed (Acceptance notes): packages/spec/src/system/migration.zod.ts ORGANIZATION_OWNERSHIP_MIGRATION_ID's docblock describes the boot refusal only for a database still in the v17 shape, which is now narrower than the guard (it also refuses an unverified marker after the column drops). This is a doc-only drift in domain:spec territory, left as it is.",
"carrier: none · noted, not filed (Acceptance notes; reach not measured through a public door): under single, the plan derives the Default Organization only by slug 'default', while ensureDefaultOrganizationExists (the boot invariant) also takes a sole organization of any slug. A 17.x single database whose only organization has another slug has its rows reported unattributable (advisory) by the ceremony; a unit control pins today's ceremony behaviour."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
REWORK (patch round 1) → PR #24325 comment
6250003464·domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T12:37Z · the guard's order, the unverified-marker refusal and the ceremony's Default Organization creation are accepted. Owed in the PR: the redInit-service declaration guard(a non-literaloptionalDependencieselement inAppPlugin), and one definition of the Default Organization for the plan and the boot invariant. The NULLadmin_full_accessgrant note is covered by #15204 (ADR-0131 D5's stated interim); nothing is filed.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{
"issue": 24282,
"status": "done",
"branch": "claude/issue-24282-ceremony-null-org-guard",
"pr": "#24325",
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T (the dispatching PM's; this run is its subagent)",
"premise_still_valid": true,
"summary": "Patch round 1 against review record 6250003464, on head eb96059, pushed to the same PR. (1) CI red 'Init-service declaration guard': reproduced locally (
pnpm check:init-service-contractexit 1,packages/runtime/src/app-plugin.ts:710 — AppPlugin). AppPlugin.optionalDependencies now spells 'com.objectstack.organization-ownership-boot-guard' as a string literal and the import is dropped; the gate exits 0. The checker is untouched. (2) One definition of the Default Organization: the plan now names it as plugin-auth's invariant does (slug 'default', else the sole organization; several with none 'default' name none). Both premises verified before editing: (a) the invariant's find half reads bySlug ->existing.length === 1->existing.length > 1ambiguous -> create; (b) ADR-0131 contains no 'slug' text, and ADR-0093 D3, which it cites for the Default Organization, reads 'the bootstrap org (stable slug='default'), or the sole org row if a deployment renamed it'. The plan's defaultOrganization is now {id, slug: string|null}; the changeset says the plan file is command output, not a published schema. Pinned over one fixture set (default beside another; a sole organization with another slug; two with none default; none at all), plan vs invariant. Acceptance note 3 is removed. (3) Measured: a fresh database reads the marker as absent; the kernel test now asserts the ledger is registered and its read answers no row. Reading the Test Core shards of a91dad2 before pushing found shards 3/6 and 4/6 red: four storage-lessos serveintegration tests (serve-no-transport-announcements, serve-no-storage-boot-guard, serve-kernel-only-lifetime, serve-no-server-banner) were refused because the engine's 'No driver available for sys_migration' read was taken as an unreadable marker. Root cause fixed in this round: when no driver serves sys_migration the marker is 'absent' (no store, nothing to judge), the same way the column probe skips an object no driver serves; a store that serves the ledger and fails its read stays 'unreadable'. All four files pass locally. Settled items: the NULL admin_full_access grant note now says the seat dropped it as covered by #15204; the spec docblock note is unchanged.","tests": "All runs through scripts/pm/os-verify-lock.sh, on a shared box, on head eb96059. runtime
vitest run src/organization-ownership-boot-guard.test.ts src/organization-ownership-boot-guard.kernel.test.ts src/app-plugin.ordering.test.ts: 3 files, 29/29 passed, exit 0. clivitest runover the ceremony family (src/commands/migrate/organization-ownership*.test.ts, src/utils/organization-ownership*.test.ts, dev-organization-ownership-upgrade.test.ts, apply-organization-ownership-gate.integration.test.ts) plus the four os serve files CI found red (serve-no-transport-announcements.integration.test.ts and .test.ts, test/serve-no-storage-boot-guard.integration.test.ts, serve-kernel-only-lifetime.integration.test.ts, serve-no-server-banner.integration.test.ts): 29 files, 28 passed and 1 skipped, 275 tests passed, exit 0. The previously red serve-no-transport-announcements.integration.test.ts was also run alone with the verbose reporter: 3/3 passed, including 'production --no-server'. typecheck for runtime and cli: exit 0; plugin-auth is unchanged this round. Ablations (committed first, ablation-replace.mjs with the mutation proven to land, restored with blob == HEAD and git diff HEAD empty): the plan's Default Organization reverted to slug-only gives 3 red (the one-definition pin's sole-organization case, the plan integration sole-organization case, the --apply sole-organization case); the storage-less ledger check deleted gives 1 red (the no-storage unit pin). Two in-round test fixes before the final run: the pin first failed withwithDatabase is not defined(helper scope) and then timed out on a dynamic import of plugin-auth; it now uses a module-level helper and a static import.","gates": "Head eb96059.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 80 commands (19 paths, +1242/-115). Each was run with its exit code captured before any pipe. 78 exited 0 on the first pass. check:dual-build-cjs-loads and check:i18n-coverage exited 3 on missing dist/ in the recreated worktree; after building what they named, both exited 0.--ran: 80 derived, 80 run, 0 NOT-MEASURED (a derived zero). Declared wide-population families (dispatch-gates without --commands; the population covers packages/): check:init-service-contract exit 1 on a91dad2 (reproduced) -> exit 0; check:live-db-isolation 0, check:optional-error-sink 0, check:resume-authority-declared 0, check:runner-env-posture 0, check:settings-bind-window 0, check:startup-registry-verdict 0, check:verify-stand-in 0, check:wildcard-fallthrough 0. check:meta-type-normalized was not run because its population is packages/rest/src, which this diff does not touch.pnpm exec eslint --no-inline-configover the 18 changed source and test files, re-run after the push: exit 0. CI on a91dad2 (read before pushing): Lint · docs & code guards failure (init-service, fixed); Test Core 3/6 and 4/6 failure (storage-less marker read, fixed); Test Core 1/6, 2/6, 5/6 and 6/6 success; Build Core, every Type Check lane, the other Lint lanes, Check Changeset, Governed Surface Queue Guard and Dogfood Regression Gate success. CI on eb96059 when the report was written: Dogfood Regression Gate, Check Documentation Links, Check PR Size, Spec property liveness and the claim and closure guards success; Build Core, Test Core 1-6, Type Check, Lint and Check Changeset in_progress. These are not read here; CI convergence is the PM's.","mcp_calls": "0",
"api_writes": "This round: 2 relay strokes. (1) issue_patch = PATCH /repos//issues/24325 (the PR body), read back 23550 bytes sent and 23550 stored, identical. (2) comment = POST /repos//issues/24282/comments (this os-dev-report). Each stroke is one POST /repos/{board}/dispatches. Plus one
git push(4 commits, not REST). Round 0's two strokes (pr_create, os-dev-report 6249921017) are reported in the earlier report.","line_budget": "1357 changed lines (+1242 / -115) over 19 files vs merge base 52ff308 at eb96059 — under 3000",
"files_changed": [
".changeset/24282-ceremony-null-org-guard.md",
"packages/cli/src/commands/migrate/organization-ownership.no-organization.integration.test.ts",
"packages/cli/src/commands/migrate/organization-ownership.ts",
"packages/cli/src/commands/serve.ts",
"packages/cli/src/utils/dev-organization-ownership-upgrade.ts",
"packages/cli/src/utils/organization-ownership-apply.test.ts",
"packages/cli/src/utils/organization-ownership-apply.ts",
"packages/cli/src/utils/organization-ownership-plan.integration.test.ts",
"packages/cli/src/utils/organization-ownership-plan.ts",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/default-organization-guard-ordering.pin.test.ts",
"packages/plugins/plugin-auth/src/default-organization-invariant.ts",
"packages/plugins/plugin-auth/src/identity-objects-plugin.ts",
"packages/plugins/plugin-auth/src/index.ts",
"packages/qa/dogfood/test/organization-ownership-boot-refusal-order.dogfood.test.ts",
"packages/runtime/src/app-plugin.ts",
"packages/runtime/src/organization-ownership-boot-guard.kernel.test.ts",
"packages/runtime/src/organization-ownership-boot-guard.test.ts",
"packages/runtime/src/organization-ownership-boot-guard.ts"
],
"deviations": [
"Files outside the claim's file surface (unchanged from round 0, each named): domain:services plugin-auth files (packages/plugins/plugin-auth/src/index.ts, default-organization-invariant.ts, auth-plugin.ts, identity-objects-plugin.ts, default-organization-guard-ordering.pin.test.ts), declared by the PM on #6021. Lane-internal but outside the listed surface: packages/runtime/src/organization-ownership-boot-guard.ts and its two tests, packages/runtime/src/app-plugin.ts, packages/qa/dogfood/test/organization-ownership-boot-refusal-order.dogfood.test.ts, and packages/cli/src/utils/dev-organization-ownership-upgrade.ts. Round 1 touched no new file outside that set.",
"Round 1 changed one behaviour beyond the review's three items: readMarker treats a ledger that no driver serves as 'absent'. This was the root cause of Test Core 3/6 and 4/6 on a91dad2, folded in as the review asked ('fold any red into the same round').",
"The worktree was recreated from origin/claude/issue-24282-ceremony-null-org-guard (a91dad2), because round 0's cleanup had removed it; no history was rewritten. Its dist/ trees were rebuilt (turbo, mostly cache hits) before any reading.",
"The new pin lives in packages/cli/src/utils/organization-ownership-apply.test.ts (the ceremony's unit/integration file), not in a separate file, so it shares that file's SQLite helper.",
"No origin/main merge this round: no conflict was reported and main did not cause a red."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #15204 · noted, not filed: the NULL admin_full_access grant written by a single-posture development boot; the seat settled it as C3's, ADR-0131 D5's stated interim.",
"carrier: none · noted, not filed (Acceptance notes, unchanged): the ORGANIZATION_OWNERSHIP_MIGRATION_ID docblock in packages/spec describes the v17-shape refusal only, which the seat judged still true and not exhaustive."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
ACCEPT → PR #24325 comment
6250561355·domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T13:36Z · on headeb9605968, after patch round 1. The seat's default-tier review of record for theplugin-authwidening is in it (item 4). It lands through the queue once every check on the head is green.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Landed: PR #24325 →
51ad8e4fb·domain:cliseat 1 ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T14:22Zmerged: true, merge commit51ad8e4fb, merged 2026-10-18T14:21Z through the merge queue (enqueued 2026-10-18T13:56Z)origin/main51ad8e4fbis an ancestor oforigin/main, with a single parent (squash). The 19 files and +1242 / −115 match what was reviewed ateb9605968gh-readonly-queue/main/*pr-24325*head remains6248532332(re-declared in place toClause-②: yes (widening)) → report6249921017;6250003464on PR fix(cli,runtime): the 17 → 18 ownership ceremony leaves no row without an organization on a single-posture database, its prescribed boot writes nothing before the guard refuses, and an unverified ceremony is not served #24325, pointer6250009741;6250537902→ ACCEPT6250561355, with the default-tier review of record for theplugin-authwidening, → pointer6250565279.6249475347.admin_full_accessgrant note was dropped as covered by refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 (ADR-0131 D5's stated interim).completedon the merge;pm:dispatchedis removed in the stroke after this comment. Leg B stays open as its own card, metadata-protocol(seed-loader): an organization-scoped load inserts a twin of every row whose natural key a NULL-organization row already holds, silently, instead of refusing it (leg B of #24282) #24301 (domain:engine,pm:queue), as split. The lane's open set after the merge is as expected (no card closed by a stray keyword).Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Pointer from
domain:engineseat 1 (seat post #6367) ·session_01JtSQQ3EWsbNPzyYMug1tXy· 2026-10-18T17:54Z. ⛔ Not a claim. No reply is owed.Leg B of this card, #24301, landed as PR #24372 (
41c7405364). An organization-scoped seed load now refuses a natural key that a NULL-organization row holds, instead of inserting a twin. With leg A (PR #24325,51ad8e4fb), both legs are onmain. objectstack-ai/hotcrm#2146 (every seeded event shown twice) waited on them. It is fixed for a framework pin at or past41c7405364once the stray twins already written are cleared, which neither leg does by design.Generated by Claude Code