Skip to content

fix(cloud-connection): an install-local reseed over an intact baseline answers success with the skipped count - #21832

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-21776-reseed-intact-baseline
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-21776-reseed-intact-baseline

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21776

Clause-②: yes (widening)

An install-local reseed over sample rows that are all still in place now answers success with the loader's skipped count. Before, it answered 422 RESEED_NO_ROWS "The package declares no seedable records for this runtime." over a package that declares 28 records. The fix implements triage 5984295768. Dispatched by the domain:cli seat (session_01RWZbGvPFcRKvUqASZtunCU), claim 5989435782.

What changed

handleReseed in packages/cloud-connection/src/marketplace-install-local-plugin.ts (now at :1860) is the only runtime change.

  • It reads skipped from the seed summary beside inserted, updated and errors. A run with nothing written, no errors and skipped above 0 is the intact baseline, and it answers 200. The install's ledger record is set as it is when rows land (withSampleData: true, sampleDataPurged: false): the rows are present.
  • Every success now answers all four counts.
  • Both refusals are unchanged, byte for byte. Errors with nothing written still answer 422 RESEED_NO_ROWS with the error count and first error. A run in which the loader processed no record still answers 422 RESEED_NO_ROWS with the existing text. Both keep details: { inserted, updated, errors }.

No change to the purge, the listing, the rehydrate or the install route. No packages/spec path. No new error code.

Response shape, before and after (for the Clause-② declaration)

Measured on a real boot (showcase with the CRM example installed, 28 seed rows):

request before (origin/main) after
reseed over the intact baseline 422 {"success":false,"error":{"code":"RESEED_NO_ROWS","message":"Reseed wrote no rows. The package declares no seedable records for this runtime.","details":{"inserted":0,"updated":0,"errors":0}}} 200 {"success":true,"data":{"manifestId":"com.example.crm","inserted":0,"updated":0,"skipped":28,"errors":0,"withSampleData":true}}
reseed after a purge 200, data: { manifestId, inserted: 28, updated: 0, errors: 0, withSampleData: true } 200, data: { manifestId, inserted: 28, updated: 0, skipped: 0, errors: 0, withSampleData: true }
reseed, every dataset scoped to another environment 422 RESEED_NO_ROWS, existing text unchanged
reseed, records error and nothing is written 422 RESEED_NO_ROWS with the error count unchanged

The before row for the intact baseline is the real-boot body that the ablation's mutated leg printed (below). The before success shape is read from origin/main source. The door now accepts a request it refused, and the success body gains skipped: widening, minor for @objectstack/cloud-connection.

Mechanism readings (the dispatch's assumptions, measured)

  • H1 holds. On 07bf21ff, handleReseed started at :1852 and computed wrote = inserted + updated > 0. With zero errors it answered the "declares no seedable records" text, with details: { inserted, updated, errors }. It did not read skipped.
  • H2: the summary already carries skipped. runInlineSeed returns skipped: result.summary.totalSkipped, and applySideEffects spreads it into seeded. No new source was needed. The install handler in the same file already counted skipped as "rows already in the database".
  • H3: "the loader reports 0 declared" is a run with all four counts at 0. The loader reconciles inserted + updated + skipped + errored against the records it processes for this runtime (SeedLoadResult in @objectstack/spec/data). With errors at 0 and skipped at 0, it processed no record. Real cases: every dataset is scoped by Seed.env to another environment (pinned on a real boot), or every dataset declares records: []. A package with NO seed dataset never reaches the loader. It answers 400 RESEED_SKIPPED (no-datasets) before this branch, and that answer is unchanged (pinned at unit level).
  • H4 holds. errors above 0 with nothing written keeps its answer, even when other records were skipped. Pinned with details unchanged.

Tests

All at head 44436429 (after merging origin/main 08adfead), unless noted.

  • Unit: packages/cloud-connection/src/marketplace-install-local-reseed.test.ts, 7 cases. They cover the intact baseline (200, the whole payload, ledger flips), rows landing (skipped: 0), no record processed (422, code, status and the existing text), errors (422, details unchanged), errors beside skipped records (422, unchanged), no dataset (400 RESEED_SKIPPED) and partial success. pnpm --filter @objectstack/cloud-connection test: 39 files, 488 tests passed. typecheck (both tsc programs) exit 0, and --listFiles shows the test file.
  • Door pin on a real boot: packages/qa/dogfood/test/install-local-reseed-intact-baseline.dogfood.test.ts, 5 cases on two boots. (1) Intact baseline: 200 with { inserted: 0, updated: 0, skipped: 28, errors: 0 }, and no row's updated_at moved. (2) After a purge: 200 with inserted: 28. (3) Every dataset scoped to another environment: 422 RESEED_NO_ROWS with the existing text, and no rows. Run together with the other three reseed-touching dogfood files (purge, listing, no-active-organization): 4 files, 25 tests passed. The dogfood tsc --noEmit exit 0 lists the new file.
  • Ablation, a one-off run (no permanent file), at dbf5e037. It uses scripts/ablation-replace.mjs in wrap mode with a trap restore. The mutation set intactBaseline to never true: the anchor skipped > 0; became skipped less-than 0;. On disk the anchor went 1 to 0 and the blob changed. Then pnpm --filter @objectstack/cloud-connection build ran, and ablation-dist-preflight found the marker in dist/ (2 built files). Results: unit 1 failed and 6 passed (the intact-baseline case: expected 422 to be 200). Dogfood 1 failed and 4 passed (the intact-baseline case, printing the before body above). Restore: blob equals HEAD and git diff HEAD is empty. After a rebuild, preflight --absent was clean and the whole tree was clean. Unit 7 out of 7 and dogfood 5 out of 5 passed again. Direction: red, as predicted.

Gates

At 44436429: node scripts/pm/dispatch-gates.mjs --commands derived 67 families. All 67 ran, each exit 0, and --ran reconciles them as "67 derived, 67 run, 0 NOT-MEASURED (a DERIVED zero)". The dispatch named 49 of them, and the derivation added 18: check-adr-0087-registration (both), check-empty-changeset (both), release-rehearsal-clone --self-test, release-pending-publish --self-test, spec check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs, check:engine-double-contract, check:objectql-double-limit, check:objectui-changeset, check:pm-changeset-deadline-census, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher. check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (exit 3: 8 packages had no dist/). It is not counted as a run. After those 8 were built, it passed with exit 0. The full pnpm lint (eslint . --no-inline-config) passed with exit 0 in 116 s.

Acceptance notes

Out of scope, measured and not addressed here

A ledger entry whose engines.protocol (^16) excludes this runtime (protocol 17) was booted on the showcase. The rehydrate refused it with OS_PROTOCOL_INCOMPATIBLE, and none of its objects were registered. Then:

  • POST …/install-local/com.example.crm/reseed-sample-data answered 400 RESEED_SKIPPED "Reseed did not run: seed-error: Object 'crm_account' not found". Before that refusal it loaded the package's translation bundle into the i18n service (2 locale loads). It also merged its 5 seed datasets into the kernel's shared seed-datasets list (0 to 5). There were 0 engine writes and no rows.
  • POST …/purge-sample-data answered 200 { deleted: 0, skipped: 0, errors: 28, withSampleData: false } and set the ledger's withSampleData from true to false. There were 0 engine writes.

This is reported to the seat for filing.


Generated by Claude Code

claude added 5 commits October 5, 2026 06:44
…e answers success with the skipped count

A reseed whose every declared seed record is already present wrote nothing
because there was nothing to write, and answered 422 RESEED_NO_ROWS "The
package declares no seedable records for this runtime" over a package that
declares them. It now answers 200 with inserted, updated, skipped and errors;
RESEED_NO_ROWS stays for a run whose records errored, and, with its text, for
a run in which the loader processed no record for this runtime.

Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-authored-by: Claude <noreply@anthropic.com>
…after a purge, and with no record for this runtime

Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-authored-by: Claude <noreply@anthropic.com>
… away from bootStack's own environment

Claude-Session: https://claude.ai/code/session_01RWZbGvPFcRKvUqASZtunCU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cloud-connection, touching 2 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via MarketplaceInstallLocalPlugin (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6fb71152ca5cc7c71723f15eaf33bb3f8f7138e8 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 565ca17573ff650bd2ccff95ca646e3b0049cbca — the merge of head 44436429616458bcccf3d2c9eabdb69328083a56 into base 6fb71152ca5cc7c71723f15eaf33bb3f8f7138e8, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 565ca17573ff650bd2ccff95ca646e3b0049cbca && git checkout 565ca17573ff650bd2ccff95ca646e3b0049cbca
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6fb71152ca5cc7c71723f15eaf33bb3f8f7138e8 44436429616458bcccf3d2c9eabdb69328083a56 && git checkout -B drift-repro 6fb71152ca5cc7c71723f15eaf33bb3f8f7138e8 && git merge --no-ff 44436429616458bcccf3d2c9eabdb69328083a56

node scripts/docs-audit/affected-docs.mjs --json 6fb71152ca5cc7c71723f15eaf33bb3f8f7138e8

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 6fb71152ca5cc7c71723f15eaf33bb3f8f7138e8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 44436429616458bcccf3d2c9eabdb69328083a56
Local-runs: none

Inputs read, nothing else: card #21776 (body, comments 5984295768 triage, 5989435782 claim, 5989949526 os-dev-report, 5989985375 ACCEPT; no earlier Contract review on the card); PR #21832 (body, 4-file list, the GitHub diff, which is byte-identical to git diff 08adfeade..44436429 at the merge-base); the 35 check-runs on the head; origin/main for the rule texts, the ledger row, and the pre-change source. The PR's head had not moved when read (44436429, draft, base main, mergeable clean). origin/main has since gained 3 commits (53021e3a, 6fb71152, 8832655a); none touches packages/cloud-connection.

① Derived judgments

Accept-set at POST /api/v1/marketplace/install-local/:manifestId/reseed-sample-data, read off the diff (handleReseed, marketplace-install-local-plugin.ts :1860 at the head, :1852 on origin/main). The only runtime change is three lines: const skipped = summary.seeded.skipped ?? 0, const intactBaseline = !wrote && errors === 0 && skipped above 0, the refusal guard if (!wrote && !intactBaseline), plus skipped added to the success body. Every other branch is textually untouched.

  1. Inline run, nothing written, zero errors, skipped above 0 (the intact baseline): before, 422 RESEED_NO_ROWS "Reseed wrote no rows. The package declares no seedable records for this runtime." with details: { inserted: 0, updated: 0, errors: 0 } (origin/main :1900–:1910); after, 200 { success: true, data: { manifestId, inserted: 0, updated: 0, skipped: N, errors: 0, withSampleData: true } }, and the ledger entry is written withSampleData: true, sampleDataPurged: false. RIGHT: it is the triage's answer (5984295768), and the loader's skipped for the reseed's defaultMode: 'upsert' (runInlineSeed :2425) is an existing row whose replay is a no-op (metadata-protocol/src/seed-loader.ts :2393–:2398), so the rows are there. The door pin confirms no row's updated_at moved.
  2. Rows land (inserted + updated above 0), with or without errors: 200 as before; the body gains the skipped key, every other key unchanged. RIGHT: additive, read from result.summary.totalSkipped, which SeedLoadResultSchema requires (spec/src/data/seed-loader.zod.ts :453), so the key is always a number.
  3. Errors above 0 and nothing written, whatever skipped is: 422 RESEED_NO_ROWS, message "Reseed wrote no rows (N errors). First error: …", details: { inserted, updated, errors }. Byte-unchanged (intactBaseline is false whenever errors !== 0). RIGHT, and pinned at unit level both with and without skipped records beside the errors.
  4. Inline run with all four counts at 0 (the loader processed no record for this runtime): 422 RESEED_NO_ROWS with the existing "declares no seedable records" text and details: { 0, 0, 0 }. Byte-unchanged. RIGHT: the loader reconciles inserted + updated + skipped + errored against the records it processes (seed-loader.ts :875), and a dataset scoped by Seed.env to another environment is not processed (datasetAllowsEnv :289), so the text now states a true cause. Pinned on a real boot with env: ['prod','test'] against bootStack's NODE_ENV=development, which the loader maps to dev (:260).
  5. seeded.mode === 'skipped' (no datasets, objectql-or-metadata-missing, a thrown seed): 400 RESEED_SKIPPED, answered at :1887 before the counts are read; mode === 'refused': 403 with the no-active-organization text at :1880; the admission, the missing-manifestId 400, the 404 and the unreadable-entry refusals. All upstream of the diff and untouched. RIGHT.
  6. One edge the widening also admits, named by the dev: a dataset declaring mode: 'update' yields skipped for a record with NO existing row (seed-loader.ts :2381–:2384), so a reseed of only such datasets over an empty table now answers 200 with skipped above 0 and records withSampleData: true. Before it answered the false-cause 422; neither answer is exact, and the imprecision is the loader's skipped conflating "present" with "nothing to update", which predates this card. No example declares mode: 'update'. Noted, not a defect of this diff.

Public surface of @objectstack/cloud-connection: package.json exports carries . alone, to dist/index.d.ts / dist/index.d.cts; src/index.ts and package.json are byte-identical to origin/main at the head; handleReseed is a private arrow property whose signature (c: any, ctx: PluginContext, returning a Promise of Response) is unchanged, so the built declaration stays private handleReseed; with no type; no exported symbol or type names the reseed payload (git grep of exported reseed in the package: 0). The built entry declarations reach nothing new. RIGHT: the yes rests on the accept-set arm alone, not on the public-surface arm.

Test changes, read against the diff: the unit file's 7 cases name the six accept-set rows above plus partial success; the two pre-existing assertions tightened from toMatchObject to toEqual on details and on the success body are strictly stronger, and the renamed "seeds nothing" case keeps its 422, code and now also its text. The new dogfood file is collected by the isolated project's glob (test/**/*.test.ts minus SHARED_SHOWCASE), so the three green Dogfood Regression Gate shards covered it.

② Semver level

Changeset .changeset/21776-reseed-intact-baseline.md: "@objectstack/cloud-connection": minor, body line Clause-②: yes (widening); the same line is line 3 of the PR body and in claim 5989435782.

The arm. execution-duties.md :67–:68: the criterion is whether the card widens the accept set or expands the public surface. Row ① 1 is a request the door refused and now accepts: the accept set widens. The public surface does not move (above). The only published text on the code is its ledger row, packages/spec/src/api/error-code-ledger.zod.ts :1038, 'RESEED_NO_ROWS', // reseed ran but wrote nothing: it describes the refusal of exactly this run (the intact reseed ran and wrote nothing), so the before-behaviour sat inside the published text and the after-behaviour exceeds it. no would need a published promise of success that the code broke; there is none, so the false-refusal reading of no does not apply. (narrowing) is BREAKING and nothing is refused that was accepted. yes (widening) is the right single arm. Its level: AGENTS.md Post-Task Checklist step 3 (:1075–:1076), yes takes at least minor; nothing is removed or renamed and every existing key keeps its value, so major is not owed. minor is right (17.6.0 to 17.7.0). skip-changeset does not apply: the package publishes.

Each changeset sentence against the diff:

  • Headline (success with the skipped count instead of a refusal naming a false cause): matches row ① 1.
  • "Intact baseline" bullet: the 200 body is listed key-for-key in the diff's order (manifestId, inserted, updated, skipped, errors, withSampleData); the before text is origin/main :1907 verbatim; "the install's record of sample data is set the same way as when rows land" matches :1930–:1933 (withSampleData = true, sampleDataPurged = false). Holds. One wording note: "every seed record the package declares is already present" is, in mechanism, every record the loader processed for this runtime; the refusal bullet carries the "for this runtime" qualifier, and no reader is misled.
  • "skipped on every success": matches row ① 2, including partial success.
  • "Unchanged refusals": the errors case with count, first error and details: { inserted, updated, errors } matches row ① 3; the same-text no-record case and the Seed.env example match row ① 4; "a package with no seed dataset at all still answers 400 RESEED_SKIPPED (no-datasets)" matches applySideEffects (:2272 default, seeded only if (opts.seedNow && datasets.length above 0) at :2359) and :1887. Holds.

Clause-②: yes (widening) — confirmed as the single arm; minor confirmed as the level; the changeset's Clause-② line is what check-adr-0087-registration reads, and Check Changeset is green on the head.

③ Boundary flags

  • open_questions[0] (which answer belongs to a package with NO seed dataset): A, keep it as built. The triage's own parenthetical defines the RESEED_NO_ROWS case as "the loader reports 0 declared", a count the loader can only report when it runs; a manifest with no datasets never reaches it (:2272, :2359), so it is not that case. The pending .changeset/21774-install-local-no-active-org.md on origin/main (patch, not yet in CHANGELOG) already lists "a package with no seed datasets" among the declines that stay 400 RESEED_SKIPPED, so B would silently contradict text already on main. The seat's ACCEPT read the same way. Answered; the PR implements A and pins it.
  • Acceptance note 1 (mode: 'update' over an empty table): accurate on source (row ① 6). No producer in examples/, the loader semantics predate the card, nothing to file against this PR. Answered.
  • Acceptance note 2 (ledger comment "reseed ran but wrote nothing" still true): true as a description of every emission, since the code is still emitted only for a run that wrote nothing; it no longer describes every run that wrote nothing, because the intact baseline now succeeds. A one-line refresh of that comment is domain:spec, fenced off this claim (no packages/spec path), and optional: not owed by this PR, and not a FAIL input. Answered.
  • Acceptance note 3 (sibling [finding] install-local: after a restart whose rehydrate refused a protocol-incompatible package, GET /install-local still lists it as installed (200, no "not loaded" marker); with PR #21820 each GET also logs a warn for it #21822 / PR fix(cloud-connection): the install-local listing marks a package the rehydrate refused as not loaded (#21822) #21833 in the same file): this diff touches only the handleReseed region (:1843–:1947); handleList and the rehydrate are untouched, matching the claim's concurrency reading. Whichever lands second takes a main merge first, as the seat's ACCEPT already says. Answered.
  • Dev deviations: (1) model-free commit trailers and the AGENTS.md footer, a process choice with no bearing on the diff, accepted by the seat; (2) 0 label writes, consistent with the dispatch; (3) the extra no-datasets unit pin, inside the ruling's spirit and accepted; (4) the one-off scratch dogfood file for carrier (d), absent from the 4-file list, so not in the diff; (5) worktree removed after push. None is a boundary breach.
  • Out-of-scope carrier (d) (reseed and purge on a rehydrate-refused entry): reported, not fixed here, and filed by the seat as [finding] install-local: reseed-sample-data on a package the rehydrate refused (protocol-incompatible) loads its translations and merges its 5 seed datasets before failing, and purge-sample-data flips its withSampleData; neither runs the handshake #21834. Not this PR's surface.
  • Check-runs on 44436429616458bcccf3d2c9eabdb69328083a56, latest run per name, 35 names: 32 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in), all path-filter or opt-in skips), 0 failure, 0 in progress. Check Changeset, Lint & Repo Gates, Governed Surface Queue Guard, the four Type Check jobs, TypeScript Type Check, Test Core and its six shards, the Dogfood Regression Gate and its three shards, Temporal Conformance and Dogfood Verify CLI are all green. No red input.
  • Fences: 4 files, all inside the claim's file surface; no packages/spec path; no new error code; purge, listing, rehydrate and install routes untouched.

Implemented-by: claude/issue-21776-reseed-intact-baseline
Reviewed-by: session_01RWZbGvPFcRKvUqASZtunCU

Independence: INDEPENDENT AGENT

VERDICT: PASS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants