You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
service-datasource: a re-import the metadata door refuses as DESTRUCTIVE_CHANGE prescribes ?force=true, which the import route never reads — a third face of #11095's class (reachable once #21788 lands) #21841
This card takes the import door's refusal prescription; parent #21788 keeps the durable import itself. Raised from #21788's in-flight build (PR #21837) by domain:services seat 1 (#6021), session_011K3zqE8Pv1Evw5hc8tZCnN. The seat owns it with the parent's domain and priority, and dispatches it directly once the parent lands.
So a re-import that would drop or transform an existing object's fields (for example, re-importing with excludeColumns that drops a column) is refused by the door's existing destructive-change guard, and the import route relays it as 400 EXTERNAL_IMPORT_ERROR.
The refusal's text prescribes "re-submit with ?force=true to proceed". The import route reads no force parameter and refuses no unknown query parameter, so following the prescription repeats the same refusal.
Mechanism:destructiveChangeRemedy (packages/metadata-protocol) has faces for the package-duplicate and meta-dispatch doors only. The import route falls to the default text, which names ?force. #11095 fixed two earlier faces of exactly this class.
Done when: a destructive re-import's refusal names a remedy that works from the import door, and a pin follows the prescription and reaches a different answer. Whether that remedy is a working force on the import route or a prescription naming the metadata door is the build's question, answered with the four-axis frame. ⛔ No silent overwrite of an existing object's definition: the refusal itself stays.
Positions: the import route in packages/services/service-datasource, and destructiveChangeRemedy in packages/metadata-protocol (engine lane; declared cross-lane at the claim).
Claim: PM loop round 3 · 2026-10-05T10:15Z
Session: session_011K3zqE8Pv1Evw5hc8tZCnN
Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-21841-import-refusal-working-remedy
Worktree: objectstack-issue-21841
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface (at origin/main2df3d13d, which carries #21788's PR #21837), per the card (a seat-owned sub-issue of #21788):
packages/services/service-datasource/src/plugin.ts (the import's saveMetaItem call) and the import route in external-datasource-service.ts. The import's destructive-change refusal names a remedy that works from the import door.
Conditional, by route: a face for the import door, which adds one member to the writeFace enum in packages/spec/src/api/protocol.zod.ts (spec lane; declared on [PM seat] domain:spec — 🟢 huangyiirene · session_01YRp5z4kK6vgYkrdH97ztsy #6017 in this act). Or a working force on the import route, which adds an accepted parameter to that route. Which one is the build's question, answered with the four-axis frame, as the card says.
Tests, a door pin in a NEW file under packages/qa/dogfood/test/ (declared on [PM seat] domain:cli — ⏳ vacant #6024 in this act), content/docs/** sentences this makes false, and a changeset.
⛔ The refusal itself stays: no silent overwrite of an existing object's definition. ⛔ No change to the existing faces' text. ⛔ No edit to the validation path (#21842's).
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier). The at-tier contract review is owed and runs before landing.
Clause-②: yes (widening)
Either route widens: a new face adds a member to a published spec enum, and a working force adds a parameter the import route accepts. Nothing that is accepted today is refused. The changeset is at least minor for every package whose published surface moves.
Thread-read: none
Serial constraints cleared: at 2026-10-05T10:15Z:
{
"issue": 21841,
"status": "done",
"branch": "claude/issue-21841-import-refusal-working-remedy",
"pr": "https://github.com/objectstack-ai/objectstack/pull/21874",
"head": "5e5dedaa13",
"session": "session_011K3zqE8Pv1Evw5hc8tZCnN (subagent run, so this is the parent PM session's id; container CLAUDE_CODE_REMOTE_SESSION_ID cse_011K3zqE8Pv1Evw5hc8tZCnN)",
"premise_still_valid": true,
"route": "An import write face, 'external-import'. No new capability on the import route.",
"summary": "The import's save (persistObject in service-datasource/src/plugin.ts) now sends writeFace: 'external-import'. destructiveChangeRemedy renders that face in the existing faces' grammar: the import route accepts no force, so import the table under a new name, or save the changed definition through PUT /api/v1/meta/object/NAME?force=true. These are the words of #21837's changeset. The enum member is added in spec (SaveMetaItemRequestSchema.writeFace) and in the local MetadataWriteFace, and the reference row is regenerated. The refusal itself stays (400 EXTERNAL_IMPORT_ERROR, stored definition unchanged). The other faces' text is unchanged. The 422 clause keeps its full-prose default on this face, because the import route relays only the message. The four axes agree, so no question goes to the maintainer.",
"hypotheses": {
"H1": "CONFIRMED. The red door pin, committed first (28bf4c43), ran on base src and the base metadata-protocol dist: Tests 3 failed | 1 passed (4). Served text: '... Field 'region' removed - existing data in this column will become inaccessible. - re-submit with ?force=true to proceed.' The follow case passed expect(followed).toBe(message) before failing at line 136, so re-submitting the import with ?force=true returned the byte-identical refusal, and the stored definition did not change.",
"H2": "CONFIRMED, with one sharpening. The spec enum member is REQUIRED for this route, not conditional. plugin.ts types its save door as Pick(MetadataProtocol, 'saveMetaItem') from @objectstack/spec, so passing the face fails typecheck without the spec member. Measured by the reverse leg: TS2820 names the 4-member union from the rebuilt d.ts.",
"H3": "MEASURED, and the business axis decides it. The external import route has 0 callers in objectui at the pinned .objectui-sha 0abd4f9f and at main f1a177c. The console's import dialog (importObjectDraft, app-shell/src/views/metadata-admin/external/api.ts) uses POST .../draft and then PUT /api/v1/meta/object/:name. Control: both of those hit in that file. In objectstack at 2df3d13d there are 0 callers of datasources.external.import( outside packages/client, and that SDK method sends its options as a JSON body with no query-string channel. The prescribed door serves the case from raw HTTP and from the SDK (meta.saveItem with force: true); the door pin measures it at 200.",
"H4": "CONFIRMED by the door pin. Remedy 1 (import under a new name) answers 201 and the new object serves 3 rows. Remedy 2 (PUT /api/v1/meta/object/NAME?force=true, with the definition from the import's own draft step) answers 200; region is gone from the stored definition and the rows still serve. Control: the same PUT without force answers 409 DESTRUCTIVE_CHANGE, stored definition unchanged."
},
"four_axes": "Business: zero pull for a force on the import route (H3); the prescribed door already serves re-imports. Long-term: follows the earlier ruling on this class, which threaded force only where a twin door already read it (the import has no such twin), and keeps destructive acknowledgement on one door. A force would also add a query parameter to a route with no closed query set, and a force to IExternalDatasourceService.importObject's published contract. AI-error: a force on the import would let a body holding only table options destructively overwrite an object under a colliding name, including one no import created; the face keeps the stricter contract. Startup focus: one enum member, one case and one call-site argument, against a new REST parameter plus a service-contract change with no caller. All four favour the face; no conflict.",
"tests": "All at HEAD 5e5dedaa13 unless noted, each under scripts/pm/os-verify-lock.sh. metadata-protocol: vitest 'Test Files 214 passed | 3 skipped (217) / Tests 27751 passed | 19 skipped'; typecheck exit 0. service-datasource: 'Test Files 37 passed (37) / Tests 718 passed (718)'; typecheck exit 0. spec: 'Test Files 668 passed (668) / Tests 19289 passed | 1 todo'; typecheck exit 0; check:generated 'All 15 generated artifacts are up to date' (after gen:docs regenerated one reference row). dogfood: typecheck exit 0; the new door pin plus #21837's pin 'Test Files 2 passed / Tests 7 passed'. tsc --listFiles includes each edited test file (count 1 in service-datasource, metadata-protocol and dogfood). ABLATION 1 (scripts/ablation-replace.mjs WRAP; plugin.ts stops stating the face; anchor x1 to x0, blob b0385ebd to 2c89cd8f; directions predicted before running): door pin 'Tests 3 failed | 1 passed (4)', served text back to 're-submit with ?force=true to proceed.'; seam pin 'Tests 2 failed | 3 passed (5)'. No rebuild was needed: the dogfood alias resolves @objectstack/service-datasource to src. Restore: blob b0385ebd == HEAD, git diff HEAD empty. ABLATION 2 (case 'external-import' unmatched in protocol.ts; blob 4e881baf to 66cce7ad; relative import, src): face inventory 'Tests 2 failed | 24 passed (26)', exactly the never-re-submit and names-the-door cases. Restore blob == HEAD. ABLATION 3 (the import face added to the 422 trimming case; blob 4e881baf to d1f48892): 'Tests 1 failed | 25 passed (26)', exactly the [COUPLING] case. Restore blob == HEAD. REVERSE TYPE LEG ('external-importt' in plugin.ts): service-datasource typecheck exit 2, TS2820 naming the union package-duplicate | meta-envelope | meta-dispatch | external-import | undefined. Restore blob == HEAD. LINT, narrowed: eslint --no-inline-config --format json over the 7 changed .ts files reported 7 files, 0 errors, 0 warnings. eslint's own config ignores the other 2 changed paths (.md, .mdx: 'no matching configuration'). eslint.config.mjs enables no type-aware linting, so the diff cannot move a verdict on an untouched file. The whole-repo pnpm lint is CI's.",
"gates": {
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, at HEAD 5e5dedaa13: 114 commands; the dispatch-time 83 are a subset (comm -23 is empty).",
"results": [
"exit 0 | node scripts/check-adr-0087-registration.mjs --base origin/main",
"exit 0 | node scripts/check-adr-0087-registration.mjs --self-test",
"exit 0 | node scripts/check-changeset-no-major.mjs --base origin/main",
"exit 0 | node scripts/check-changeset-no-major.mjs --self-test",
"exit 0 | node scripts/check-ci-filter-parity.mjs",
"exit 0 | node scripts/check-closing-keyword-parity.mjs",
"exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test",
"exit 0 | node scripts/check-comment-mask-adoption.mjs",
"exit 0 | node scripts/check-comment-mask-adoption.mjs --self-test",
"exit 0 | node scripts/check-comment-mask-corpus.mjs",
"exit 0 | node scripts/check-dev-prereqs.mjs --self-test",
"exit 0 | node scripts/check-doc-frontmatter.mjs",
"exit 0 | node scripts/check-doc-frontmatter.mjs --self-test",
"exit 0 | node scripts/check-doc-route-spelling.mjs --advisory",
"exit 0 | node scripts/check-doc-route-spelling.mjs --self-test",
"exit 0 | node scripts/check-docs-section-name.mjs",
"exit 0 | node scripts/check-docs-section-name.mjs --self-test",
"exit 0 | node scripts/check-dts-emitted.mjs --self-test",
"exit 0 | node scripts/check-empty-changeset.mjs --base origin/main",
"exit 0 | node scripts/check-empty-changeset.mjs --self-test",
"exit 0 | node scripts/check-issue-citations.mjs",
"exit 0 | node scripts/check-keyed-text-bounds.mjs",
"exit 0 | node scripts/check-keyed-text-bounds.mjs --self-test",
"exit 0 | node scripts/check-platform-object-tenancy-census.mjs",
"exit 0 | node scripts/check-platform-object-tenancy-census.mjs --self-test",
"exit 0 | node scripts/check-plugin-teardown-shape.mjs",
"exit 0 | node scripts/check-plugin-teardown-shape.mjs --self-test",
"exit 0 | node scripts/check-registry-log-declared.mjs",
"exit 0 | node scripts/check-registry-log-declared.mjs --self-test",
"exit 0 | node scripts/check-rest-log-spy-declared.mjs",
"exit 0 | node scripts/check-rest-log-spy-declared.mjs --self-test",
"exit 0 | node scripts/check-section-landing-index.mjs",
"exit 0 | node scripts/check-section-landing-index.mjs --self-test",
"exit 0 | node scripts/check-spec-docblock-symbol-anchors.mjs",
"exit 0 | node scripts/check-spec-docblock-symbol-anchors.mjs --self-test",
"exit 0 | node scripts/check-system-context-census.mjs",
"exit 0 | node scripts/check-system-context-census.mjs --self-test",
"exit 0 | node scripts/check-tenant-audit-census.mjs",
"exit 0 | node scripts/check-tenant-audit-census.mjs --self-test",
"exit 0 | node scripts/check-undeclared-dep-imports.mjs",
"exit 0 | node scripts/check-undeclared-dep-imports.mjs --self-test",
"exit 0 | node scripts/docs-audit/check-affected-docs.mjs",
"exit 0 | node scripts/docs-audit/check-drift-comment.mjs",
"exit 0 | node scripts/pm/release-rehearsal-clone.mjs --self-test",
"exit 0 | node scripts/release-pending-publish.mjs --self-test",
"exit 0 | pnpm --filter @objectstack/lint run check:doc-formula-expressions",
"exit 0 | pnpm --filter @objectstack/lint run check:doc-security-posture",
"exit 0 | pnpm --filter @objectstack/spec run check:api-surface",
"exit 0 | pnpm --filter @objectstack/spec run check:authorable-surface",
"exit 0 | pnpm --filter @objectstack/spec run check:browser-reachable-entries",
"exit 0 | pnpm --filter @objectstack/spec run check:docs",
"exit 0 | pnpm --filter @objectstack/spec run check:dual-source-exports",
"exit 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys",
"exit 0 | pnpm --filter @objectstack/spec run check:empty-state",
"exit 0 | pnpm --filter @objectstack/spec run check:entry-nameability",
"exit 0 | pnpm --filter @objectstack/spec run check:export-origins",
"exit 0 | pnpm --filter @objectstack/spec run check:exported-any",
"exit 0 | pnpm --filter @objectstack/spec run check:generated",
"exit 0 | pnpm --filter @objectstack/spec run check:liveness",
"exit 0 | pnpm --filter @objectstack/spec run check:llms-txt",
"exit 0 | pnpm --filter @objectstack/spec run check:objectui-pin-citations",
"exit 0 | pnpm --filter @objectstack/spec run check:skill-examples",
"exit 0 | pnpm --filter @objectstack/spec run check:skill-refs",
"exit 0 | pnpm --filter @objectstack/spec run check:strictness-ledger",
"exit 0 | pnpm --filter @objectstack/spec run check:variant-docs",
"exit 0 | pnpm --filter @objectstack/spec run check:yaml-examples",
"exit 0 | pnpm check:changeset-gate-self-tests",
"exit 0 | pnpm check:corpus-claim-drift",
"exit 0 | pnpm check:cross-package-test-inputs",
"exit 0 | pnpm check:dispatcher-error-vocabulary",
"exit 0 | pnpm check:doc-anchors",
"exit 0 | pnpm check:doc-authoring",
"exit 0 | pnpm check:docs-audit-scope",
"exit 0 | pnpm check:docs-redirects",
"exit 0 | pnpm check:docs-single-h1",
"exit 0 | pnpm check:docs-spec-enumerations",
"exit 0 | pnpm check:docs-transcript-drift",
"exit 0 | pnpm check:driver-memory-census",
"exit 0 | pnpm check:dts-closure",
"exit 0 | pnpm check:dual-build-cjs-loads",
"exit 0 | pnpm check:durability-log-level",
"exit 0 | pnpm check:engine-double-contract",
"exit 0 | pnpm check:filter-alias-parity",
"exit 0 | pnpm check:gitlink-declared",
"exit 0 | pnpm check:issue-citations",
"exit 0 | pnpm check:lean-entry-closure",
"exit 0 | pnpm check:logger-receiver-detach",
"exit 0 | pnpm check:merge-driver",
"exit 0 | pnpm check:nul-bytes",
"exit 0 | pnpm check:objectql-double-limit",
"exit 0 | pnpm check:objectui-changeset",
"exit 0 | pnpm check:org-identifier",
"exit 0 | pnpm check:page-declaration-shape",
"exit 0 | pnpm check:pm-changeset-deadline-census",
"exit 0 | pnpm check:pm-prior-rulings",
"exit 0 | pnpm check:pm-widening-tells",
"exit 0 | pnpm check:published-files",
"exit 0 | pnpm check:published-readme-links",
"exit 0 | pnpm check:query-options-erasure",
"exit 0 | pnpm check:quick-reference-counts",
"exit 0 | pnpm check:react-page-adapter-contract",
"exit 0 | pnpm check:refd-timer-probe",
"exit 0 | pnpm check:role-word",
"exit 0 | pnpm check:skill-identifier-liveness",
"exit 0 | pnpm check:slot-lookup",
"exit 0 | pnpm check:sourcemap-no-sources-content",
"exit 0 | pnpm check:spec-parsed-alias",
"exit 0 | pnpm check:test-source-alias",
"exit 0 | pnpm check:tier-file-adoption",
"exit 0 | pnpm check:type-check-coverage",
"exit 0 | pnpm check:type-check-debt",
"exit 0 | pnpm check:vendor-version-stamps",
"exit 0 | pnpm check:watch-hint-literal",
"exit 0 | pnpm check:where-matcher"
],
"notes": "Two gates first exited 3 (PREREQUISITE NOT MET, nothing measured): spec check:skill-examples needed client-react's dist, and check:dual-build-cjs-loads needed 8 packages' dist. Both were built under the lock (41 turbo tasks, cache hits) and re-run to exit 0. The exit codes above are the re-runs.",
"ran_verdict": "dispatch-gates --ran: 114 derived famil(ies) accounted for - 114 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)."
},
"line_budget": "n/a",
"deviations": [
"The dispatch's file surface puts the import route in external-datasource-service.ts. It lives in packages/rest/src/external-datasource-routes.ts. It was not edited: it already relays the message verbatim.",
"Changeset grades: @objectstack/spec and @objectstack/metadata-protocol are minor (the enum and parameter widen); @objectstack/service-datasource is patch, because its published exports and types do not move. The changeset carries the line-start 'Clause-②: yes (widening)'. check:changeset-no-major is exit 0.",
"Three existing unit-test files outside packages/qa were edited, all inside the declared 'unit tests' surface: metadata-protocol's protocol.destructive-409-face-inventory.test.ts (row 8 and section 6), service-datasource's external-import-saves-through-metadata-door.test.ts (the exact request now carries the face, plus a no-smuggle case), and spec's protocol.test.ts (the closed-vocabulary loop). No packages/qa file was edited; the door pin is a new file.",
"The branch is not merged with origin/main: 4 commits behind (2df3d13d..9f9510f2), and none of them touches the 9 paths. dispatch-gates warned that one gate input is stale (scripts/engine-double-contract.pinned.json); it concerns fake engines, and this diff adds none.",
"Lock: two acquisitions returned 99 (queue-timeout behind another worktree's closure build) and were re-acquired under the same OS_VERIFY_LOCK_SLOT. Nothing is reported from a 99.",
"Commit trailers use AGENTS.md's model-free pair (Co-authored-by: Claude), not the harness reminder's model-named spelling; the pre-push trailer check passed on every push.",
"The worktree was removed after PR creation (git worktree remove, no --force; the tree was clean)."
],
"files_changed": [
".changeset/21841-import-refusal-working-remedy.md",
"content/docs/references/api/protocol.mdx",
"packages/metadata-protocol/src/protocol.destructive-409-face-inventory.test.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/qa/dogfood/test/external-import-destructive-remedy.dogfood.test.ts",
"packages/services/service-datasource/src/__tests__/external-import-saves-through-metadata-door.test.ts",
"packages/services/service-datasource/src/plugin.ts",
"packages/spec/src/api/protocol.test.ts",
"packages/spec/src/api/protocol.zod.ts"
],
"mcp_calls": "0 - no MCP tool was called.",
"api_writes": "3 - each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet-write relay: (1) pr_create, executed as POST /repos/objectstack-ai/objectstack/pulls (draft), run 37310410738, body read back identical; (2) label-write --assign os-steve on PR #21874, run 37310493837, read back matching; (3) this os-dev-report comment via scripts/pm/post-stamped.mjs, POST /repos/objectstack-ai/objectstack/issues/21841/comments. git push is not counted (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none - noted, not filed. The face-inventory docblock (protocol.destructive-409-face-inventory.test.ts) still lists the compound-name PUT /meta/:type/:a/:b as row 2. That route was retired by commit 7986d973f. This is pre-existing drift in a test comment. Dedupe words: compound-name retired, face inventory row 2, destructive-409 docblock.",
"carrier: none - noted, not filed. @objectstack/rest lists @objectstack/metadata-protocol as a devDependency, so its dist bundles a full copy of the protocol, destructiveChangeRemedy included. The running protocol service comes from metadata-protocol's own plugin, so rest's copy of the new case is unreachable from rest. Observation only. Dedupe words: rest bundles metadata-protocol, devDependency bundling, duplicate protocol copy.",
"carrier: none - not measured, noted in the PR's Acceptance notes. The console's import dialog (objectui importObjectDraft) saves through PUT /meta/object/:name and sends no force, so a console re-import that shrinks an object would get the meta-envelope 409, whose ?force=true the dialog does not offer. objectui-side; not reproduced here. Dedupe words: importObjectDraft force, console re-import destructive, objectui import dialog 409."
]
}
Verdict on the dev report 5994592838 (status: done), checked against GitHub and the tree. Accepted as built. The claim's Clause-②: yes (widening) stands, and the path limb also hits (packages/spec/src/api/protocol.zod.ts). An at-tier contract review is owed before landing.needs:contract-review is put on the PR in this act.
Route: an import write face, 'external-import'. No new capability on the import route.
The import's save (persistObject in service-datasource/src/plugin.ts) states the face server-side. destructiveChangeRemedy renders it in the existing faces' grammar: the import route accepts no force, so import under a new name, or save the changed definition through PUT /api/v1/meta/object/:name?force=true. These are the words of fix(service-datasource)!: Import as Object saves through the metadata door's save #21837's changeset.
The enum member is added to SaveMetaItemRequestSchema.writeFace (spec) and to the local MetadataWriteFace (protocol). The spec member is required, not conditional: plugin.ts types its save door from the spec's MetadataProtocol, and the reverse type leg fails with TS2820 without it.
The refusal stays (400 EXTERNAL_IMPORT_ERROR, stored definition unchanged). The other faces' text is unchanged. The 422 clause keeps its full-prose default on this face, because the import route relays only the message.
Four axes, as measured by the build; no conflict, so nothing goes to the maintainer:
Business: the import route has no first-party caller that re-imports. The console's import dialog uses the draft step and then PUT /api/v1/meta/object/:name (objectui at the pin 0abd4f9f), so a force on the import would have nothing pulling on it.
AI-error: a force on the import would let a body of table options overwrite a colliding object, including one no import created.
Startup: one enum member, one case and one call-site argument.
Checked:
The door pin follows both remedies to a different answer: a new name answers 201 and serves the rows; the forced PUT answers 200 and the dropped field is gone. Control: the same PUT without force answers 409 DESTRUCTIVE_CHANGE.
Ablations (scripts/ablation-replace.mjs): the face not stated turned 3 door legs and 2 seam pins red; the protocol case unmatched turned the 2 face-inventory cases red; the face added to the 422 trimming case turned the coupling case red. Every restore was proven.
The changeset grades match what publishes: @objectstack/spec and @objectstack/metadata-protocolminor, @objectstack/service-datasourcepatch. The PR body and the changeset carry the line-start Clause-②: yes (widening).
Surface notes (accepted): the import route lives in packages/rest/src/external-datasource-routes.ts, not where the order said, and it was not edited because it already relays the message verbatim. Three existing unit-test files outside packages/qa were edited, all inside the declared unit-test surface.
Carried, not filed (each in the PR's Acceptance notes): the face-inventory docblock still lists the retired compound-name PUT as a row; @objectstack/rest bundles a copy of the protocol through a devDependency; the console's import dialog sends no force on its own PUT, so a console re-import that shrinks an object meets the envelope 409 the dialog does not offer to force (objectui side, not reproduced here).
Fixes #21841 closed this card completed. In this act the seat clears the pm:dispatched state label and the assignee os-steve. The domain:services, area:api, priority:p2 and bug labels stay.
What shipped: a destructive re-import through "Import as Object" is still refused (400 EXTERNAL_IMPORT_ERROR, stored definition unchanged). Its message now names remedies that work from the import route: import the table under a new name, or save the changed definition through PUT /api/v1/meta/object/:name?force=true. The import route gains no force. SaveMetaItemRequestSchema.writeFace and the protocol's saveMetaItem parameter gain the server-stated member 'external-import': @objectstack/spec and @objectstack/metadata-protocol ship minor, and @objectstack/service-datasource ships patch.
Carried, recorded in the PR's Acceptance notes: a stale row in the face-inventory docblock, @objectstack/rest bundling a protocol copy through a devDependency, and the console import dialog's own PUT offering no force (objectui side, not reproduced).
Activity
objectstack-fleet commentedon Oct 5, 2026
Claim: PM loop round 3 · 2026-10-05T10:15Z
Session:
session_011K3zqE8Pv1Evw5hc8tZCnNAccount:
os-steve(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-21841-import-refusal-working-remedyWorktree:
objectstack-issue-21841Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (at
origin/main2df3d13d, which carries #21788's PR #21837), per the card (a seat-owned sub-issue of #21788):packages/services/service-datasource/src/plugin.ts(the import'ssaveMetaItemcall) and the import route inexternal-datasource-service.ts. The import's destructive-change refusal names a remedy that works from the import door.destructiveChangeRemedyinpackages/metadata-protocol/src/protocol.ts(engine lane; declared on [PM seat] domain:engine — 🟢 marchtian #6367 in this act).writeFaceenum inpackages/spec/src/api/protocol.zod.ts(spec lane; declared on [PM seat] domain:spec — 🟢 huangyiirene · session_01YRp5z4kK6vgYkrdH97ztsy #6017 in this act). Or a workingforceon the import route, which adds an accepted parameter to that route. Which one is the build's question, answered with the four-axis frame, as the card says.packages/qa/dogfood/test/(declared on [PM seat] domain:cli — ⏳ vacant #6024 in this act),content/docs/**sentences this makes false, and a changeset.⛔ The refusal itself stays: no silent overwrite of an existing object's definition. ⛔ No change to the existing faces' text. ⛔ No edit to the validation path (#21842's).
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier). The at-tier contract review is owed and runs before landing.Clause-②: yes (widening)
forceadds a parameter the import route accepts. Nothing that is accepted today is refused. The changeset is at leastminorfor every package whose published surface moves.Thread-read: none
Serial constraints cleared: at 2026-10-05T10:15Z:
07e933be). No open PR touchesmetadata-protocol/src/protocol.ts,packages/spec/src/api/protocol.zod.tsorservice-datasource/src/. The engine seat post [PM seat] domain:engine — 🟢 marchtian #6367 holds no claim on that region since [finding] lock family, artifact layer × package axis: the _lock gate looks up the packaged artifact with no package while both reads look it up with the request's package #21803 landed.POST /external/validatedoes not see a federated object saved at runtime (throughPUT /meta/objector the import) until the next restart #21842 (p3, the validate door) sharesexternal-datasource-service.tsin a different region and stays in the queue.hotlong's security(datasource): credential-shaped values in some datasource configurations are stored and served without the platform's secret handling — detail withheld pending maintainer #21840 (area:access) declaresservice-datasource/srcat directory level. That is ordinary concurrency, and the later lander mergesmain.Selection: a seat-owned sub-issue in the parent's domain and priority (p2), unblocked by fix(service-datasource)!: Import as Object saves through the metadata door's save #21837's landing. The other p2 the seat holds, plugin-security: a data-door edit of a permission set saved into a writable runtime package forks it — the write-through's update leg saves without the row's package, leaving two active
sys_metadatarows for one name #21861, waits on fix(plugin-security): the permission-set lock reads the row's provenance, so org-owned sets, clones and runtime-package sets edit again #21857's merge.Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
os-dev-report
{ "issue": 21841, "status": "done", "branch": "claude/issue-21841-import-refusal-working-remedy", "pr": "https://github.com/objectstack-ai/objectstack/pull/21874", "head": "5e5dedaa13", "session": "session_011K3zqE8Pv1Evw5hc8tZCnN (subagent run, so this is the parent PM session's id; container CLAUDE_CODE_REMOTE_SESSION_ID cse_011K3zqE8Pv1Evw5hc8tZCnN)", "premise_still_valid": true, "route": "An import write face, 'external-import'. No new capability on the import route.", "summary": "The import's save (persistObject in service-datasource/src/plugin.ts) now sends writeFace: 'external-import'. destructiveChangeRemedy renders that face in the existing faces' grammar: the import route accepts no force, so import the table under a new name, or save the changed definition through PUT /api/v1/meta/object/NAME?force=true. These are the words of #21837's changeset. The enum member is added in spec (SaveMetaItemRequestSchema.writeFace) and in the local MetadataWriteFace, and the reference row is regenerated. The refusal itself stays (400 EXTERNAL_IMPORT_ERROR, stored definition unchanged). The other faces' text is unchanged. The 422 clause keeps its full-prose default on this face, because the import route relays only the message. The four axes agree, so no question goes to the maintainer.", "hypotheses": { "H1": "CONFIRMED. The red door pin, committed first (28bf4c43), ran on base src and the base metadata-protocol dist: Tests 3 failed | 1 passed (4). Served text: '... Field 'region' removed - existing data in this column will become inaccessible. - re-submit with ?force=true to proceed.' The follow case passed expect(followed).toBe(message) before failing at line 136, so re-submitting the import with ?force=true returned the byte-identical refusal, and the stored definition did not change.", "H2": "CONFIRMED, with one sharpening. The spec enum member is REQUIRED for this route, not conditional. plugin.ts types its save door as Pick(MetadataProtocol, 'saveMetaItem') from @objectstack/spec, so passing the face fails typecheck without the spec member. Measured by the reverse leg: TS2820 names the 4-member union from the rebuilt d.ts.", "H3": "MEASURED, and the business axis decides it. The external import route has 0 callers in objectui at the pinned .objectui-sha 0abd4f9f and at main f1a177c. The console's import dialog (importObjectDraft, app-shell/src/views/metadata-admin/external/api.ts) uses POST .../draft and then PUT /api/v1/meta/object/:name. Control: both of those hit in that file. In objectstack at 2df3d13d there are 0 callers of datasources.external.import( outside packages/client, and that SDK method sends its options as a JSON body with no query-string channel. The prescribed door serves the case from raw HTTP and from the SDK (meta.saveItem with force: true); the door pin measures it at 200.", "H4": "CONFIRMED by the door pin. Remedy 1 (import under a new name) answers 201 and the new object serves 3 rows. Remedy 2 (PUT /api/v1/meta/object/NAME?force=true, with the definition from the import's own draft step) answers 200; region is gone from the stored definition and the rows still serve. Control: the same PUT without force answers 409 DESTRUCTIVE_CHANGE, stored definition unchanged." }, "four_axes": "Business: zero pull for a force on the import route (H3); the prescribed door already serves re-imports. Long-term: follows the earlier ruling on this class, which threaded force only where a twin door already read it (the import has no such twin), and keeps destructive acknowledgement on one door. A force would also add a query parameter to a route with no closed query set, and a force to IExternalDatasourceService.importObject's published contract. AI-error: a force on the import would let a body holding only table options destructively overwrite an object under a colliding name, including one no import created; the face keeps the stricter contract. Startup focus: one enum member, one case and one call-site argument, against a new REST parameter plus a service-contract change with no caller. All four favour the face; no conflict.", "tests": "All at HEAD 5e5dedaa13 unless noted, each under scripts/pm/os-verify-lock.sh. metadata-protocol: vitest 'Test Files 214 passed | 3 skipped (217) / Tests 27751 passed | 19 skipped'; typecheck exit 0. service-datasource: 'Test Files 37 passed (37) / Tests 718 passed (718)'; typecheck exit 0. spec: 'Test Files 668 passed (668) / Tests 19289 passed | 1 todo'; typecheck exit 0; check:generated 'All 15 generated artifacts are up to date' (after gen:docs regenerated one reference row). dogfood: typecheck exit 0; the new door pin plus #21837's pin 'Test Files 2 passed / Tests 7 passed'. tsc --listFiles includes each edited test file (count 1 in service-datasource, metadata-protocol and dogfood). ABLATION 1 (scripts/ablation-replace.mjs WRAP; plugin.ts stops stating the face; anchor x1 to x0, blob b0385ebd to 2c89cd8f; directions predicted before running): door pin 'Tests 3 failed | 1 passed (4)', served text back to 're-submit with ?force=true to proceed.'; seam pin 'Tests 2 failed | 3 passed (5)'. No rebuild was needed: the dogfood alias resolves @objectstack/service-datasource to src. Restore: blob b0385ebd == HEAD, git diff HEAD empty. ABLATION 2 (case 'external-import' unmatched in protocol.ts; blob 4e881baf to 66cce7ad; relative import, src): face inventory 'Tests 2 failed | 24 passed (26)', exactly the never-re-submit and names-the-door cases. Restore blob == HEAD. ABLATION 3 (the import face added to the 422 trimming case; blob 4e881baf to d1f48892): 'Tests 1 failed | 25 passed (26)', exactly the [COUPLING] case. Restore blob == HEAD. REVERSE TYPE LEG ('external-importt' in plugin.ts): service-datasource typecheck exit 2, TS2820 naming the union package-duplicate | meta-envelope | meta-dispatch | external-import | undefined. Restore blob == HEAD. LINT, narrowed: eslint --no-inline-config --format json over the 7 changed .ts files reported 7 files, 0 errors, 0 warnings. eslint's own config ignores the other 2 changed paths (.md, .mdx: 'no matching configuration'). eslint.config.mjs enables no type-aware linting, so the diff cannot move a verdict on an untouched file. The whole-repo pnpm lint is CI's.", "gates": { "derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, at HEAD 5e5dedaa13: 114 commands; the dispatch-time 83 are a subset (comm -23 is empty).", "results": [ "exit 0 | node scripts/check-adr-0087-registration.mjs --base origin/main", "exit 0 | node scripts/check-adr-0087-registration.mjs --self-test", "exit 0 | node scripts/check-changeset-no-major.mjs --base origin/main", "exit 0 | node scripts/check-changeset-no-major.mjs --self-test", "exit 0 | node scripts/check-ci-filter-parity.mjs", "exit 0 | node scripts/check-closing-keyword-parity.mjs", "exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test", "exit 0 | node scripts/check-comment-mask-adoption.mjs", "exit 0 | node scripts/check-comment-mask-adoption.mjs --self-test", "exit 0 | node scripts/check-comment-mask-corpus.mjs", "exit 0 | node scripts/check-dev-prereqs.mjs --self-test", "exit 0 | node scripts/check-doc-frontmatter.mjs", "exit 0 | node scripts/check-doc-frontmatter.mjs --self-test", "exit 0 | node scripts/check-doc-route-spelling.mjs --advisory", "exit 0 | node scripts/check-doc-route-spelling.mjs --self-test", "exit 0 | node scripts/check-docs-section-name.mjs", "exit 0 | node scripts/check-docs-section-name.mjs --self-test", "exit 0 | node scripts/check-dts-emitted.mjs --self-test", "exit 0 | node scripts/check-empty-changeset.mjs --base origin/main", "exit 0 | node scripts/check-empty-changeset.mjs --self-test", "exit 0 | node scripts/check-issue-citations.mjs", "exit 0 | node scripts/check-keyed-text-bounds.mjs", "exit 0 | node scripts/check-keyed-text-bounds.mjs --self-test", "exit 0 | node scripts/check-platform-object-tenancy-census.mjs", "exit 0 | node scripts/check-platform-object-tenancy-census.mjs --self-test", "exit 0 | node scripts/check-plugin-teardown-shape.mjs", "exit 0 | node scripts/check-plugin-teardown-shape.mjs --self-test", "exit 0 | node scripts/check-registry-log-declared.mjs", "exit 0 | node scripts/check-registry-log-declared.mjs --self-test", "exit 0 | node scripts/check-rest-log-spy-declared.mjs", "exit 0 | node scripts/check-rest-log-spy-declared.mjs --self-test", "exit 0 | node scripts/check-section-landing-index.mjs", "exit 0 | node scripts/check-section-landing-index.mjs --self-test", "exit 0 | node scripts/check-spec-docblock-symbol-anchors.mjs", "exit 0 | node scripts/check-spec-docblock-symbol-anchors.mjs --self-test", "exit 0 | node scripts/check-system-context-census.mjs", "exit 0 | node scripts/check-system-context-census.mjs --self-test", "exit 0 | node scripts/check-tenant-audit-census.mjs", "exit 0 | node scripts/check-tenant-audit-census.mjs --self-test", "exit 0 | node scripts/check-undeclared-dep-imports.mjs", "exit 0 | node scripts/check-undeclared-dep-imports.mjs --self-test", "exit 0 | node scripts/docs-audit/check-affected-docs.mjs", "exit 0 | node scripts/docs-audit/check-drift-comment.mjs", "exit 0 | node scripts/pm/release-rehearsal-clone.mjs --self-test", "exit 0 | node scripts/release-pending-publish.mjs --self-test", "exit 0 | pnpm --filter @objectstack/lint run check:doc-formula-expressions", "exit 0 | pnpm --filter @objectstack/lint run check:doc-security-posture", "exit 0 | pnpm --filter @objectstack/spec run check:api-surface", "exit 0 | pnpm --filter @objectstack/spec run check:authorable-surface", "exit 0 | pnpm --filter @objectstack/spec run check:browser-reachable-entries", "exit 0 | pnpm --filter @objectstack/spec run check:docs", "exit 0 | pnpm --filter @objectstack/spec run check:dual-source-exports", "exit 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys", "exit 0 | pnpm --filter @objectstack/spec run check:empty-state", "exit 0 | pnpm --filter @objectstack/spec run check:entry-nameability", "exit 0 | pnpm --filter @objectstack/spec run check:export-origins", "exit 0 | pnpm --filter @objectstack/spec run check:exported-any", "exit 0 | pnpm --filter @objectstack/spec run check:generated", "exit 0 | pnpm --filter @objectstack/spec run check:liveness", "exit 0 | pnpm --filter @objectstack/spec run check:llms-txt", "exit 0 | pnpm --filter @objectstack/spec run check:objectui-pin-citations", "exit 0 | pnpm --filter @objectstack/spec run check:skill-examples", "exit 0 | pnpm --filter @objectstack/spec run check:skill-refs", "exit 0 | pnpm --filter @objectstack/spec run check:strictness-ledger", "exit 0 | pnpm --filter @objectstack/spec run check:variant-docs", "exit 0 | pnpm --filter @objectstack/spec run check:yaml-examples", "exit 0 | pnpm check:changeset-gate-self-tests", "exit 0 | pnpm check:corpus-claim-drift", "exit 0 | pnpm check:cross-package-test-inputs", "exit 0 | pnpm check:dispatcher-error-vocabulary", "exit 0 | pnpm check:doc-anchors", "exit 0 | pnpm check:doc-authoring", "exit 0 | pnpm check:docs-audit-scope", "exit 0 | pnpm check:docs-redirects", "exit 0 | pnpm check:docs-single-h1", "exit 0 | pnpm check:docs-spec-enumerations", "exit 0 | pnpm check:docs-transcript-drift", "exit 0 | pnpm check:driver-memory-census", "exit 0 | pnpm check:dts-closure", "exit 0 | pnpm check:dual-build-cjs-loads", "exit 0 | pnpm check:durability-log-level", "exit 0 | pnpm check:engine-double-contract", "exit 0 | pnpm check:filter-alias-parity", "exit 0 | pnpm check:gitlink-declared", "exit 0 | pnpm check:issue-citations", "exit 0 | pnpm check:lean-entry-closure", "exit 0 | pnpm check:logger-receiver-detach", "exit 0 | pnpm check:merge-driver", "exit 0 | pnpm check:nul-bytes", "exit 0 | pnpm check:objectql-double-limit", "exit 0 | pnpm check:objectui-changeset", "exit 0 | pnpm check:org-identifier", "exit 0 | pnpm check:page-declaration-shape", "exit 0 | pnpm check:pm-changeset-deadline-census", "exit 0 | pnpm check:pm-prior-rulings", "exit 0 | pnpm check:pm-widening-tells", "exit 0 | pnpm check:published-files", "exit 0 | pnpm check:published-readme-links", "exit 0 | pnpm check:query-options-erasure", "exit 0 | pnpm check:quick-reference-counts", "exit 0 | pnpm check:react-page-adapter-contract", "exit 0 | pnpm check:refd-timer-probe", "exit 0 | pnpm check:role-word", "exit 0 | pnpm check:skill-identifier-liveness", "exit 0 | pnpm check:slot-lookup", "exit 0 | pnpm check:sourcemap-no-sources-content", "exit 0 | pnpm check:spec-parsed-alias", "exit 0 | pnpm check:test-source-alias", "exit 0 | pnpm check:tier-file-adoption", "exit 0 | pnpm check:type-check-coverage", "exit 0 | pnpm check:type-check-debt", "exit 0 | pnpm check:vendor-version-stamps", "exit 0 | pnpm check:watch-hint-literal", "exit 0 | pnpm check:where-matcher" ], "notes": "Two gates first exited 3 (PREREQUISITE NOT MET, nothing measured): spec check:skill-examples needed client-react's dist, and check:dual-build-cjs-loads needed 8 packages' dist. Both were built under the lock (41 turbo tasks, cache hits) and re-run to exit 0. The exit codes above are the re-runs.", "ran_verdict": "dispatch-gates --ran: 114 derived famil(ies) accounted for - 114 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)." }, "line_budget": "n/a", "deviations": [ "The dispatch's file surface puts the import route in external-datasource-service.ts. It lives in packages/rest/src/external-datasource-routes.ts. It was not edited: it already relays the message verbatim.", "Changeset grades: @objectstack/spec and @objectstack/metadata-protocol are minor (the enum and parameter widen); @objectstack/service-datasource is patch, because its published exports and types do not move. The changeset carries the line-start 'Clause-②: yes (widening)'. check:changeset-no-major is exit 0.", "Three existing unit-test files outside packages/qa were edited, all inside the declared 'unit tests' surface: metadata-protocol's protocol.destructive-409-face-inventory.test.ts (row 8 and section 6), service-datasource's external-import-saves-through-metadata-door.test.ts (the exact request now carries the face, plus a no-smuggle case), and spec's protocol.test.ts (the closed-vocabulary loop). No packages/qa file was edited; the door pin is a new file.", "The branch is not merged with origin/main: 4 commits behind (2df3d13d..9f9510f2), and none of them touches the 9 paths. dispatch-gates warned that one gate input is stale (scripts/engine-double-contract.pinned.json); it concerns fake engines, and this diff adds none.", "Lock: two acquisitions returned 99 (queue-timeout behind another worktree's closure build) and were re-acquired under the same OS_VERIFY_LOCK_SLOT. Nothing is reported from a 99.", "Commit trailers use AGENTS.md's model-free pair (Co-authored-by: Claude), not the harness reminder's model-named spelling; the pre-push trailer check passed on every push.", "The worktree was removed after PR creation (git worktree remove, no --force; the tree was clean)." ], "files_changed": [ ".changeset/21841-import-refusal-working-remedy.md", "content/docs/references/api/protocol.mdx", "packages/metadata-protocol/src/protocol.destructive-409-face-inventory.test.ts", "packages/metadata-protocol/src/protocol.ts", "packages/qa/dogfood/test/external-import-destructive-remedy.dogfood.test.ts", "packages/services/service-datasource/src/__tests__/external-import-saves-through-metadata-door.test.ts", "packages/services/service-datasource/src/plugin.ts", "packages/spec/src/api/protocol.test.ts", "packages/spec/src/api/protocol.zod.ts" ], "mcp_calls": "0 - no MCP tool was called.", "api_writes": "3 - each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet-write relay: (1) pr_create, executed as POST /repos/objectstack-ai/objectstack/pulls (draft), run 37310410738, body read back identical; (2) label-write --assign os-steve on PR #21874, run 37310493837, read back matching; (3) this os-dev-report comment via scripts/pm/post-stamped.mjs, POST /repos/objectstack-ai/objectstack/issues/21841/comments. git push is not counted (not REST).", "open_questions": [], "out_of_scope_findings": [ "carrier: none - noted, not filed. The face-inventory docblock (protocol.destructive-409-face-inventory.test.ts) still lists the compound-name PUT /meta/:type/:a/:b as row 2. That route was retired by commit 7986d973f. This is pre-existing drift in a test comment. Dedupe words: compound-name retired, face inventory row 2, destructive-409 docblock.", "carrier: none - noted, not filed. @objectstack/rest lists @objectstack/metadata-protocol as a devDependency, so its dist bundles a full copy of the protocol, destructiveChangeRemedy included. The running protocol service comes from metadata-protocol's own plugin, so rest's copy of the new case is unreachable from rest. Observation only. Dedupe words: rest bundles metadata-protocol, devDependency bundling, duplicate protocol copy.", "carrier: none - not measured, noted in the PR's Acceptance notes. The console's import dialog (objectui importObjectDraft) saves through PUT /meta/object/:name and sends no force, so a console re-import that shrinks an object would get the meta-envelope 409, whose ?force=true the dialog does not offer. objectui-side; not reproduced here. Dedupe words: importObjectDraft force, console re-import destructive, objectui import dialog 409." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
Review: PR #21874 at
5e5dedaa· seatdomain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T12:47ZVerdict on the dev report
5994592838(status: done), checked against GitHub and the tree. Accepted as built. The claim'sClause-②: yes (widening)stands, and the path limb also hits (packages/spec/src/api/protocol.zod.ts). An at-tier contract review is owed before landing.needs:contract-reviewis put on the PR in this act.Route: an import write face,
'external-import'. No new capability on the import route.persistObjectinservice-datasource/src/plugin.ts) states the face server-side.destructiveChangeRemedyrenders it in the existing faces' grammar: the import route accepts noforce, so import under a newname, or save the changed definition throughPUT /api/v1/meta/object/:name?force=true. These are the words of fix(service-datasource)!: Import as Object saves through the metadata door's save #21837's changeset.SaveMetaItemRequestSchema.writeFace(spec) and to the localMetadataWriteFace(protocol). The spec member is required, not conditional:plugin.tstypes its save door from the spec'sMetadataProtocol, and the reverse type leg fails withTS2820without it.400 EXTERNAL_IMPORT_ERROR, stored definition unchanged). The other faces' text is unchanged. The422clause keeps its full-prose default on this face, because the import route relays only the message.Four axes, as measured by the build; no conflict, so nothing goes to the maintainer:
PUT /api/v1/meta/object/:name(objectui at the pin0abd4f9f), so aforceon the import would have nothing pulling on it.?force=trueon routes that never thread it — the compound-namePUT /meta/:type/:a/:band the runtime dispatcherPUT /meta#11095's rule (aforceonly where a twin door already reads one), and destructive acknowledgement stays on one door.forceon the import would let a body of table options overwrite a colliding object, including one no import created.Checked:
201and serves the rows; the forcedPUTanswers200and the dropped field is gone. Control: the samePUTwithoutforceanswers409 DESTRUCTIVE_CHANGE.scripts/ablation-replace.mjs): the face not stated turned 3 door legs and 2 seam pins red; the protocol case unmatched turned the 2 face-inventory cases red; the face added to the422trimming case turned the coupling case red. Every restore was proven.@objectstack/specand@objectstack/metadata-protocolminor,@objectstack/service-datasourcepatch. The PR body and the changeset carry the line-startClause-②: yes (widening).Surface notes (accepted): the import route lives in
packages/rest/src/external-datasource-routes.ts, not where the order said, and it was not edited because it already relays the message verbatim. Three existing unit-test files outsidepackages/qawere edited, all inside the declared unit-test surface.Carried, not filed (each in the PR's Acceptance notes): the face-inventory docblock still lists the retired compound-name
PUTas a row;@objectstack/restbundles a copy of the protocol through a devDependency; the console's import dialog sends noforceon its ownPUT, so a console re-import that shrinks an object meets the envelope409the dialog does not offer to force (objectui side, not reproduced here).Generated by Claude Code
objectstack-fleet commentedon Oct 5, 2026
Landed · seat
domain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T14:02Ze864db56, which is an ancestor oforigin/main(verified withgit merge-base --is-ancestorafter a fetch). Merged at 2026-10-05T14:01Z. It landed after an at-tier contract review PASS on its head (5995209979).Fixes #21841closed this cardcompleted. In this act the seat clears thepm:dispatchedstate label and the assigneeos-steve. Thedomain:services,area:api,priority:p2andbuglabels stay.400 EXTERNAL_IMPORT_ERROR, stored definition unchanged). Its message now names remedies that work from the import route: import the table under a newname, or save the changed definition throughPUT /api/v1/meta/object/:name?force=true. The import route gains noforce.SaveMetaItemRequestSchema.writeFaceand the protocol'ssaveMetaItemparameter gain the server-stated member'external-import':@objectstack/specand@objectstack/metadata-protocolshipminor, and@objectstack/service-datasourceshipspatch.@objectstack/restbundling a protocol copy through a devDependency, and the console import dialog's ownPUToffering noforce(objectui side, not reproduced).Generated by Claude Code
fix(service-datasource)!: Import as Object saves through the metadata…
fix(service-datasource): a destructive re-import's refusal names the …
fix(service-datasource): external validate sees a federated object sa…