Skip to content

fix(rest)!: format=json honours an explicit ?fields= - #24206

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24183-export-json-fields
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24183-export-json-fields

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24183

Clause-②: no (narrowing)

BREAKING (a served-set narrowing at a REST door): with ?fields=, each row of GET /api/v1/data/:object/export?format=json now carries exactly the named keys. It ships as minor under the launch-window convention. The changeset carries the same Clause-② line and one ADR-0087 disposition (not-required (no-migration-prescription)). This narrowing owes one CONTRACT_REVIEW_TIER review of record before the queue.

Why Fixes: this PR now lands both halves of the card. The explicit-fields half was the first round. The no-fields half was a fork: the claim's premise failed when measured (see "The no-fields fork" below), and the seat decided D in comment 6238218754. JSON without fields writes each row as the read returns it, which is what QuerySchema.fields declares (optional, "Fields to retrieve": absent means no projection). Patch round 1 pins that answer through the real route, id included, and the route doc states it. The fork section below is kept as the measurement record. (This paragraph and the first line were edited by the seat; the dev writes the body once.)

What changed

  • packages/rest/src/data-action-routes.ts, JSON arm of the export handler: when ?fields= names columns, each row is cut to the resolved fields list (pickExportKeys), the same list the CSV and XLSX arms write. formatRowForJson then formats the row as before. Keys come out in list order. A named key the read did not return is left out of the row. It is not written as null.
  • fieldsExplicit records that the caller named the columns. Nothing after it narrows or reorders an explicit list: the hidden-field filter, the FLS projection and the masked-row fallback all run only on the schema-derived default. So the JSON arm reads the one resolved column list and does not define a second one.
  • The route's doc comment for fields= now says what each format does with it, and that JSON without fields writes each row as the read returns it.
  • Not changed: the CSV and XLSX arms and their default column set, formatRowForJson and export-format.ts, stripSearchCompanionFromRead, packages/objectql, packages/spec and objectui. format=json without fields is byte-for-byte the same as before.

Does the CSV arm always write some extra key? Measured: no

?format=csv&fields=title returns the single column Title, both on the showcase and in the unit pin. So the JSON projection mirrors nothing beyond the named keys.

Reach, re-measured on examples/app-showcase

Setup: a pnpm dev -- --fresh boot of this worktree, signed in as the seeded admin. The shipped admin sets carry no allowExport, so I saved an environment permission set granting allowRead and allowExport on showcase_task and assigned it to the admin.

GET /api/v1/data/showcase_task/export?format=json&fields=title, 10 rows:

  • before (8a88901be): 25 keys per row: id, created_at, updated_at, organization_id, created_by, updated_by, owner_id, owning_business_unit_id, title, project, assignee, status, priority, estimate_hours, progress, done, due_date, start_date, end_date, location, cover, labels, notes, sync_status, sync_error.
  • after (this branch): every row is exactly { "title": … }.

After the fix, ?format=json&fields=status,title,id,organization_id,__search returns rows that carry status, title, id and organization_id, in that order. __search is absent because the read strips it. The list door answers the same way: in the unit harness, GET /data/note?$select=title,__search returns rows with title only.

Unchanged after the fix: the no-fields JSON key set, and the CSV header with and without fields=title. I compared shapes, because each fresh boot seeds new ids.

The no-fields fork: the claim's premise, measured, fails

The claim's premise was that the default set (schema fields minus hidden, ∩ FLS-readable) contains id and every key the JSON re-import reads back. It does not contain id. showcase_task does not declare id, and the registry injects no id field, so id is not in the schema the default reads from.

  • No-fields JSON today: the 25 keys listed above.
  • The default set: 22 keys, matching the CSV no-fields header label for label. They are created_by, updated_at, updated_by, owner_id, title, project, assignee, status, priority, estimate_hours, progress, done, due_date, start_date, end_date, created_at, location, cover, labels, notes, sync_status, sync_error.
  • The default would drop three keys:
    • id;
    • organization_id, which is hidden and carries Default Organization;
    • owning_business_unit_id, which is hidden and null.

The import runner reads id back: it keeps a supplied id on insert and matches on it for update and upsert. I re-imported the export through POST /api/v1/data/showcase_task/import with format: json and dryRun: true:

rows sent writeMode / matchFields answer
no-fields JSON as served today upsert / id 10 updated, 0 created
the same rows cut to the default set upsert / id 0 updated, 10 created: every record duplicated
the same rows cut to the default set update / id 10 skipped

The options and a recommendation are in the os-dev-report comment on #24183, for the seat to decide.

Tests

packages/rest/src/export-json-fields.test.ts exercises the real export route: a real RestServer on a real ObjectStackProtocolImplementation on a real ObjectQL, with a better-sqlite3 :memory: driver. The companion is declared by its real producer. The protocol's findData is observed, so every pin shows the rows the read handed over next to what the route answered.

  • CSV control (the measurement above): ?fields=title writes the single Title column, while the read returned id, title, internal_code and done.
  • JSON ?fields=title: every row is exactly { title }.
  • JSON ?fields=done,internal_code,title,id: keys come out in list order, and values keep the export formatting (done becomes 是 / 否).
  • JSON ?fields=title,__search: the stripped companion is absent from the row and is never written as null. For the same list, CSV writes an empty Search Index column.
  • JSON over two chunks (60 rows, page=50, two reads): every row carries exactly id.
  • JSON with no security service composed: the projection still holds.

Ablation, through scripts/ablation-replace.mjs. The anchor must hit, and the restore is checked against the blob. The test reaches data-action-routes.ts through relative ./rest-server imports, so it runs from src/ and does not involve dist/.

  • Mutation: const row = fieldsExplicit ? pickExportKeys(rows[i], fields!) : rows[i]; became const row = rows[i]; /* ablated-24183 */. The anchor count went from 1 to 0, and the blob from e78162e654a3 to 4c922008fbbb.
  • Result: 5 failed, 1 passed. All five JSON pins went red. For example, expected [ [ 'id', 'created_at', …(4) ], …(1) ] to deeply equal … on the order pin. The CSV control stayed green, as it should, since the mutation leaves the CSV arm alone.
  • Restore leg: the blob after restore is e78162e654a3, which equals HEAD, and git diff HEAD is empty.

Suite runs on 881f6797e:

  • pnpm --filter @objectstack/rest test: exit 0, 293 files passed, 6019 tests passed, 326 skipped.
  • pnpm --filter @objectstack/rest typecheck: exit 0. The new test file is in the tsconfig.test.json program (--listFiles: 1 hit).
  • pnpm exec eslint --no-inline-config on the two changed source files: exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 881f6797e: 63 derived commands, all exit 0. The --ran reconciliation answers: 63 derived, 63 run, 0 NOT-MEASURED, every exit code recorded.
  • Not run locally: test:repo (CI's Test Core).

Acceptance notes


Generated by Claude Code

The export route declares `fields=a,b,c` for every format. CSV and XLSX
wrote exactly the listed columns; the JSON arm wrote each row as the read
returned it. With an explicit list, each JSON row now carries exactly the
named keys, in list order, read from the same resolved `fields` the other
two arms write. A named key the read did not return stays absent.

format=json without ?fields= is unchanged.

Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
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 17, 2026
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

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 — 18 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 f963d8e2b32b11be6d8d50d3134e4f3ee888e16a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4d6443fc78666b7382e8179bb6eb8a6ad397d264 — the merge of head 939c8ac1a7fa3f14259029ef662f6d82c2f4ad5d into base f963d8e2b32b11be6d8d50d3134e4f3ee888e16a, 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 4d6443fc78666b7382e8179bb6eb8a6ad397d264 && git checkout 4d6443fc78666b7382e8179bb6eb8a6ad397d264
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f963d8e2b32b11be6d8d50d3134e4f3ee888e16a 939c8ac1a7fa3f14259029ef662f6d82c2f4ad5d && git checkout -B drift-repro f963d8e2b32b11be6d8d50d3134e4f3ee888e16a && git merge --no-ff 939c8ac1a7fa3f14259029ef662f6d82c2f4ad5d

node scripts/docs-audit/affected-docs.mjs --json f963d8e2b32b11be6d8d50d3134e4f3ee888e16a

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

The seat decided the no-fields fork as D: `QuerySchema.fields` absent
means no projection, so json without `fields` writes each row as the
read returns it, `id` included. One pin through the real route asserts
each row's key set equals the observed read's, and that the read carries
a key the csv default drops, so a cut to that default reddens it.

Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 939c8ac1a7fa3f14259029ef662f6d82c2f4ad5d
Local-runs: none
Scope: packages/rest/src/data-action-routes.ts, packages/rest/src/export-json-fields.test.ts, .changeset/24183-export-json-fields.md + packages/rest/src/export-format.ts, packages/spec/src/api/contract.zod.ts, packages/spec/src/data/query.zod.ts, packages/core/src/utils/import-runner.ts, packages/rest/src/import-prepare.ts, examples/app-showcase/src/data/objects/task.object.ts, scripts/check-changeset-no-major.mjs, scripts/check-adr-0087-registration.mjs, objectui packages/plugin-list/src/ListView.tsx, objectui packages/plugin-grid/src/ObjectGrid.tsx, objectui packages/data-objectstack/src/index.ts

PR #24206 on card #24183, narrowing face · domain:cli seat 2 · session session_01QyoWKK9RwUNxBxmevy9Neb

Inputs: the card body and all five comments (triage 6236168153, claim 6237380493, reports 6238180833 and 6238602315, seat decision 6238218754); the PR body, its file list (3 files, +266/−3) and the net diff against main (merge base 8a88901; the three-dot diff against current main f7af839 is the same 3-file diff); the head's check-runs. Read-only: no worktree, build, test, gate or ablation was run; the two ablations are the dev's reports, not re-run here.

Check-runs on the head, read at 2026-10-17T18:43Z: 47 check-runs, 39 success, 8 skipped (paths-filtered: Build Docs, Console Pin Gate, the Dogfood pair, Temporal Conformance, Packed-tarball smoke, one Auto Label and one Check PR Size), 0 failure, 0 in progress. All five required contexts are success: Lint & Repo Gates, TypeScript Type Check, Test Core (every shard, 1/6 to 6/6), Build Core, Governed Surface Queue Guard. Check Changeset is success on both of its runs on this head.

① Derived judgments

Every accept-set or served-set change the diff implies, each judged:

  1. Served-set narrowing at GET /api/v1/data/:object/export with format=json and ?fields= — each row now carries exactly the named keys, in list order (data-action-routes.ts :166 pickExportKeys, :986 fieldsExplicit, :1212 the pick before formatRowForJson). Right. It is the route's pre-existing declaration honoured (fields=a,b,c was declared for every format with no caveat; at the head the doc comment :770–775 states what each format does with it), the same resolved list the CSV and XLSX arms write, and consistent with the spec: ExportRequestSchema (contract.zod.ts :420) is QuerySchema intersected with { format }, and QuerySchema.fields (query.zod.ts :519) reads "Fields to retrieve". The one named producer (objectui ListView.tsx :4848–4870 and ObjectGrid.tsx :4552–4570, through data-objectstack exportDownload :5009–5016, which serialises fields as a comma list) sends the view's visible columns and only downloads the blob; no consumer in this repo (packages/cli, packages/client, examples, content/docs, skills) sends format=json with fields and reads a key it did not name. Pinned through the real route (test :149, :157, :178, :189).

  2. An explicit list is never narrowed or reordered after fieldsExplicit (the hunk's claim at :983–986). Right, read in the code: the hidden-field default runs only under !fields || fields.length === 0 (:1022), the security-service projection only under fieldsFromSchema (:1071), the first-row derivation only under !fields (:1156), and the masked-row fallback only under !readableProjected && fieldsFromSchema (:1171). So the JSON arm reads the one resolved column list and defines no second one.

  3. A named key the read did not return is absent from the JSON row, never written as null (:166–176, :1202–1209; changeset line 15). Right. The read middleware deletes unreadable keys (the FieldMasker note at :1159–1170) and the read strips the __search companion; pickExportKeys keeps only own keys whose value is not undefined, so a stored null is still written as null and only absence is absence. CSV and XLSX write an empty cell for the same name (export-format.ts :390–397 formatRowCells); the doc comment and the changeset both state the per-format difference, and the test pins both faces side by side (:168). The list door's $select answers the same way for the same reason — the dev's scratch probe, not pinned here and not needed, since the FieldMasker note in this file already states it.

  4. Values keep the export's readable formatting and keys keep list order after the pick (changeset line 15; test :157). Right: formatRowForJson (export-format.ts :404–418) walks Object.keys(row) and formats only keys with metadata, adding none, so the insertion order pickExportKeys set is the output order; a lookup / user / reference field still resolves to a name because $expand nests the related record in the field's own value (export-format.ts :5–6, :346–351 formatReference), so no sibling key is needed and nothing is lost by picking first.

  5. format=json without ?fields= is byte-identical to before (PR body; changeset line 16). Right: the rows[i] arm at :1212 is the pre-existing statement, and formatRowForJson returns the original reference when nothing needs formatting. The route doc :775 now states it. The seat's decision (6238218754) is pinned at test :195 through the real route, id included, with an anti-vacuity check (the read carries the hidden internal_code; the CSV default header lacks Internal Code), so a cut to the CSV default would redden it.

  6. A hidden field named explicitly is served in JSON (test :157 names internal_code). Right and consistent with the CSV arm: the rest(export): a schema-derived CSV export carries an empty "Search Index" column, because the export column set does not skip the hidden __search search companion #24143 hidden-field drop is column selection on the default only, and metaMap keeps every field (:1022–1037).

  7. Public surface: pickExportKeys is module-private; packages/rest exports, packages/spec, export-format.ts, the import side and objectui are untouched; the route's OpenAPI metadata (:1263) is a summary only; content/docs/references/api/export.mdx is generated from export.zod.ts, which declares no per-format column rule; the QA checklist's export clause (records-forms.json :488) is about formats and filtered rows, not columns. Right: no doc surface, generated artifact or checklist item is owed by this diff.

  8. Noted, no action: a name repeated in an explicit list (fields=title,title) yields two CSV columns and one JSON key — inherent to a JSON object, not a claim the diff makes.

② Semver level

  • The changeset .changeset/24183-export-json-fields.md grades '@objectstack/rest': minor (line 2), declares **BREAKING** (a served-set narrowing) (line 11) and carries Clause-②: no (narrowing) (line 7), the same line the PR body carries. Matches what the diff publishes: the only published source moved is packages/rest/src/data-action-routes.ts; fewer keys are served and no new key lands on any payload, so no with the (narrowing) arm is the well-formed reading under the Post-Task Checklist rule ((narrowing) is BREAKING), and the level is the one check-changeset-no-major.mjs prescribes for a declared narrowing during the launch window (minor; major is forbidden). Triage 6236168153 had written yes (narrowing); the claim's no (narrowing) is the right reading, since clause ② is a new key on a published payload and this diff adds none. Clause-②: line: right.
  • ADR-0087 disposition (changeset line 9, the adr-0087 marker): not-required (no-migration-prescription). Right category: nothing an author can write (spec key, export, option, config field, stored shape) is removed, renamed or re-shaped, so objectstack migrate meta has nothing to rewrite and no FROM → TO mapping is owed; the body carries no rewrite prescription in arrow or table form (the closing "What changes for you" sentence is a note on request shape for an API caller, not a metadata migration); runtime-interface-only does not apply because no exported TS declaration changed. check-adr-0087-registration ran green on the head inside Check Changeset, and the dev's local run reads the same.
  • skip-changeset would be wrong here and is not used. The title fix(rest)!: carries the bang.

③ Boundary flags

Dev flags (reports 6238180833 and 6238602315) and open questions, each answered:

  • open_questions[0], round 0 — which column set for no-fields JSON. Answered by the seat's decision 6238218754 (keep the row as the read returns it) and closed in round 1 (open_questions: []). Judged, not adopted: the claim's premise (the CSV default contains id and every key the JSON re-import reads back) is measured false, and the measurement holds in the cited code — showcase_task (task.object.ts :20–120) declares no id field, so the 22-key default is the 18 declared fields plus the four readable audit and ownership columns; and the import runner (import-runner.ts :904–906) respects a supplied id on create and locates update and upsert targets by matchFields (:734–741; request parsing in import-prepare.ts :290–347), which is exactly why rows cut to that default re-import as duplicates under upsert on id. Against that, the CSV-default alternative is a silent duplication trap, the CSV-default-plus-id alternative is a second JSON-only definition of the export's columns (the defect class the claim fenced), and the id-in-every-format alternative widens the CSV and XLSX default that fix(rest)!: a schema-derived export skips the fields declared hidden #24178 just set, out of this card's scope. The declared contract (QuerySchema.fields optional, "Fields to retrieve") already answers the no-fields case as no projection, so the decision rests on spec text, not on preference. The route doc and the test header state it; test :195 pins it. Closed.
  • Deviation: Done-when part 2 open at round 0. Closed by round 1; the PR's first line is now Fixes #24183, and the "Part-of PR must not also close its card" and "The card this PR closes must claim this branch" checks are green on the head.
  • Deviation: -- --maxWorkers=2 discarded by vitest (round 0). No effect on scope (the whole package ran); round 1 ran without the bare separator. Noted.
  • Deviation: test:repo not run locally. Test Core is green on all six shards on the head. Closed.
  • Deviation: whole-workspace build for the showcase measurement; worktree recreated in round 1; check:dual-build-cjs-loads first read exit 3 (prerequisite not met), then green after a full build. Not a gate matter; Lint & Repo Gates is green on the head. Closed.
  • Deviation: changeset not edited in round 1. Right: line 16 already states the no-fields answer and nothing in the file reads as open.
  • Deviation: one extra mutation run (the CSV-default cut) to prove the new pin can fail. Within this review's read-only shape it is the dev's report; the gate verdict is the test's pass on the head. Noted.
  • Acceptance note 1 — the CSV and XLSX default carries no id on an object that declares none. Matches the route's documented default (schema fields minus hidden), pre-dates fix(rest)!: a schema-derived export skips the fields declared hidden #24178, and no named consumer needs CSV round-trip by id: noted, not filed, and not escalated here. The trigger to revisit is a named consumer.
  • Acceptance note 2 — CSV empty cell versus JSON absent key for a name the read did not return. Stated in the doc comment and the changeset; judged right in ①.3.
  • Serial constraint and single-writer path. fix(rest)!: a schema-derived export skips the fields declared hidden #24178 merged before this claim (aaaeeac); the "No other open PR may claim the same single-writer path" check is green on the head.
  • Governed surfaces: none in the file list; this review is owed by the narrowing face alone.
  • Escalated: nothing.

Implemented-by: claude/issue-24183-export-json-fields
Reviewed-by: session_01QyoWKK9RwUNxBxmevy9Neb

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · PR #24206 at 939c8ac1a (#24183)

domain:cli seat 2 · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-17T18:44Z · holder of claim 6237380493. The dev's reports are comments 6238180833 and 6238602315 on #24183, and the seat reviewed them against this PR, not the reports' own account.

  1. Shape. Draft → main. The first line is Fixes #24183 (edited by the seat from Part of once decision D was pinned, along with the "Why" paragraph); it is the only closing keyword in the body. Clause-②: no (narrowing) is line-initial in the body, the changeset and the claim. The assignee is marchtian. 3 files: data-action-routes.ts, a new route-level test, one changeset. check-governed-merges.mjs --pr 24206: NOT governed, 269 changed lines.

  2. Premise, measured. H1 held: the JSON arm wrote formatRowForJson(rows[i], …) from an unprojected read. The showcase answered 25 keys for fields=title before, and {title} after.

    The claim's no-fields decision rested on a falsifiable premise, which the dev measured false: the CSV default carries no id, and an export-then-upsert duplicated 10 records. The fork was reported, not forced. Seat decision D (6238218754): no-fields JSON writes the read's row, which is QuerySchema.fields' declared "absent = no projection" (ExportRequestSchema = QuerySchema ∩ {format}). Patch round 1 pins it through the real route, and a one-time mutation to option A reddens only that pin.

    Seat check of the diff: pickExportKeys is module-private and keeps own, non-undefined keys in list order. fieldsExplicit is captured before any default or projection runs, so the JSON arm reuses the one resolved column list and defines no second one.

  3. Contract review of record: CONTRACT_REVIEW_TIER, narrowing face, an isolated agent, PASS 6238818395 on 939c8ac1a, read at source with a matching Head-sha:. ② is right: '@objectstack/rest': minor, fix(rest)!:, no (narrowing) and the ADR-0087 marker not-required (no-migration-prescription). Triage's yes (narrowing) was the wrong spelling; nothing widens.

  4. Check-runs on head 939c8ac1a at this act: 32 success, 8 skipped, 0 other (40 runs, latest per name). check-expected-skips --pr 24206: OK. Lint & Repo Gates, TypeScript Type Check and Test Core 1–6 are success; Check Changeset is success with the breaking declaration.

  5. Accepted deviations:

    • the no-fields half came in round 1, not round 0, through the declared fork path;
    • a bare -- was dropped in the round 0 test command (it ran the whole package, no scope effect);
    • a whole-workspace build was used for the showcase measurement.

    Changeset, read sentence by sentence: the narrowing sentence and the "absent, not null" sentence match pickExportKeys. "Unchanged: format=json without ?fields= still writes each row as the read returns it" is decision D. The CSV / XLSX columns are untouched, as the claim held off.

  6. Out of scope: the CSV / XLSX default carries no id on an object that declares none. It matches the documented default, pre-dates fix(rest)!: a schema-derived export skips the fields declared hidden #24178 and has no named consumer, so it stays in Acceptance notes with carrier: #24183's decision record and is not filed. Revisit it when a named consumer needs CSV round-trip by id.

  7. Landing. Ordinary queue landing, armed in this act. Fixes #24183 closes the card on merge, and the seat strips pm:dispatched in the same act.


Generated by Claude Code

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

Development

Successfully merging this pull request may close these issues.

rest(export): format=json ignores ?fields=, so a list view's JSON export carries every readable field instead of the view's columns

2 participants