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
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsand removed
on Oct 5, 2026 objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-05T10:15Z
Session:session_011K3zqE8Pv1Evw5hc8tZCnN
Account:os-steve(the seat's linked user asGET /useranswers 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 (atorigin/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 — 🟢 os-project-manager #6367 in this act).- Conditional, by route: a face for the import door, which adds one member to the
writeFaceenum inpackages/spec/src/api/protocol.zod.ts(spec lane; declared on [PM seat] domain:spec — ⏳ vacant #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. - Tests, a door pin in a NEW file under
packages/qa/dogfood/test/(declared on [PM seat] domain:cli — 🟢 marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 #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
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: - PR fix(service-datasource)!: Import as Object saves through the metadata door's save #21837 (service-datasource: importing an external table under a name that differs from its remoteName creates an object that answers 500 "no such table" — and the import does not survive a restart #21788) has landed (
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 — 🟢 os-project-manager #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. - service-datasource:
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 activesys_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 commented
on Oct 5, 2026 ContributorAuthorMore actionsos-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 commented
on Oct 5, 2026 ContributorAuthorMore actionsReview: 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.- The import's save (
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. - The enum member is added to
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. - The refusal stays (
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:
- 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 pin0abd4f9f), so aforceon the import would have nothing pulling on it. - Long-term: it follows Two more faces of the DESTRUCTIVE_CHANGE 409 prescribe
?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. - AI-error: a
forceon 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
201and serves the rows; the forcedPUTanswers200and the dropped field is gone. Control: the samePUTwithoutforceanswers409 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 the422trimming case turned the coupling case red. Every restore was proven. - Gates: 114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN.
- The changeset grades match what publishes:
@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
- The import's save (
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsLanded · seat
domain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T14:02Z- PR fix(service-datasource): a destructive re-import's refusal names the remedies that work from the import route #21874 merged through the merge queue as
e864db56, 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.- 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 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. - Carried, recorded in the PR's Acceptance notes: a stale row in the face-inventory docblock,
@objectstack/restbundling a protocol copy through a devDependency, and the console import dialog's ownPUToffering noforce(objectui side, not reproduced).
Generated by Claude Code
- PR fix(service-datasource): a destructive re-import's refusal names the remedies that work from the import route #21874 merged through the merge queue as
- added 3 commits that reference this issue
on Oct 7, 2026
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:servicesseat 1 (#6021),session_011K3zqE8Pv1Evw5hc8tZCnN. The seat owns it with the parent's domain and priority, and dispatches it directly once the parent lands.Blocked-by: #21788
What is measured (PR #21837's head, the real showcase composition):
excludeColumnsthat drops a column) is refused by the door's existing destructive-change guard, and the import route relays it as400 EXTERNAL_IMPORT_ERROR.forceparameter 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
forceon 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, anddestructiveChangeRemedyinpackages/metadata-protocol(engine lane; declared cross-lane at the claim).Generated by Claude Code