Repository navigation
crm_contact models a postal address as five flat text fields while crm_account and crm_lead use Field.address() — and AGENTS.md's own guidance names Field.address() for exactly this #1836
Description
Activity
- addedmetadataDeclarative metadata — schema, security posture, UI surfacesDeclarative metadata — schema, security posture, UI surfaces
on Sep 9, 2026 Ruling: batch #163 item 3 · convert (
crm_contactmodels its mailing address as oneField.address()likecrm_accountandcrm_lead, per AGENTS.md's own field-type guidance; re-measure #664 first; the shipped import template keeps its five columns, mapped into the compound field's parts; existing data converted once) · maintainer 「163 同意」 2026-09-18T14:09ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation convert; the maintainer agreed. Facts (this card): three objects carry a postal address, two asField.address()and the contact as five flatmailing_*text fields; AGENTS.md's Field Type Guidance namesField.address()for a mailing address; the only record of the flat choice is a mapping-file comment stating its consequence, not its reason; #664 (address values rendering as raw JSON, closed) is the plausible historical cause and must be re-measured against the pinned console before acting. Mainstream: Salesforce Contact.MailingAddress is a compound field, the same shape as Account.BillingAddress.Ruling — convert
- Precondition: re-measure Address fields render as raw JSON on the record detail page #664 on the pinned
@objectstack/console— aField.address()value must render as a unit on the contact detail page. If it does not, stop and report; the conversion waits on the renderer, ⛔ not on a flat-field workaround. crm_contactdeclaresmailing_address: Field.address({ … }); the fivemailing_*fields retire in the same change (ADR-0049 shape, with the app's own retirement discipline).- Import contract:
assets/import-templates/contacts.csvkeeps its five customer-facing columns;contact_import.mapping.tsmaps them into the compound field'sstreet/city/state/postalCode/countryparts. ⛔ The customer's template does not change. - The The five
crm_contact.mailing_*fields: the CSV importer writes them and the authored contact form may show none of them — clear the last 5field-no-consumerswarnings blocking #1581 (epic #1579) #1827 form section renders the one field; labels collapse from five rows per locale to one;content/docs/sales/contacts.mdxand its.zh-Hans/.zh-Hantsiblings say one structured field; existing rows are converted once by a one-off system write. - The reason is recorded in
contact.object.tsin one sentence, so the next reader sees an authored decision.
⛔ 保留平铺 would keep the app's own guidance contradicted by one of three objects.
Four-facet reading: ① three objects, one shape, the repo's guidance holds; ② a contact's address is an everyday field; ③ an AI following the guidance no longer hits an exception; ④ convergence to an existing shape, no new one.
Execution
needs-user-decision→pm:queue;metadatastays. Covers #1199's fivemailing_*rows.Clause-②: no.
Generated by Claude Code
- Precondition: re-measure Address fields render as raw JSON on the record detail page #664 on the pinned
- addedpm:queueReady for the PM dispatch loopReady for the PM dispatch loopand removedneeds-user-decisionNeeds the maintainer's call before work proceedsNeeds the maintainer's call before work proceeds
on Sep 18, 2026 - addedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchand removedpm:queueReady for the PM dispatch loopReady for the PM dispatch loop
on Sep 25, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsClaim: PM loop round R64 (serial dispatch, maintainer instruction verbatim: 「串行派发」) · 2026-09-25T13:59Z
Session:session_01X8U3asekbiC7yWoEPWR4Dg
Branch:claude/issue-1836-contact-mailing-address
Worktree:hotcrm-issue-1836
Domain:repo:hotcrm(single-lane repo — nodomain:*taxonomy)
Seat:repo:hotcrm#1
File surface:src/sales/objects/contact.object.ts,src/sales/mappings/contact_import.mapping.ts,src/sales/views/contact.view.ts,src/sales/translations/{en,zh-CN,es-ES,ja-JP}/objects.customer.ts, contact seeds undersrc/sales/data/if they carrymailing_*, a one-off conversion script in thescripts/backfill-*.tsshape,content/docs/sales/contacts{,.zh-Hans,.zh-Hant}.mdx, affected tests, one.changeset/. ⛔assets/import-templates/contacts.csvdoes NOT change (ruling item 3).src/sales/mappings/account_import.mapping.tsonly if it truly depends on contact'smailing_*(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus— default judgement tier, hand-picked (dispatch-gates --tierrefuses hotcrm paths from the objectstack checkout)
Clause-②: no
Thread-read: 5731202390
Serial constraints cleared: open PRs — #1956 (src/sales/profiles/**,account.hook.ts, revenue objects, docsadministration/revenue), #1960 (#1828: revenue line-item hooks,scripts/backfill-line-number.ts, tests), #1950 (src/sales/objects/opportunity*, epic). None touchescontact.object.ts, the contact mapping/view, orobjects.customer.ts; #1950's file list does not includeobjects.customer.ts. #1902 (seed pricing,src/sales/data/) is queued behind this card.objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsos-dev-report
{
"issue": 1836,
"status": "blocked",
"branch": "claude/issue-1836-contact-mailing-address",
"pr": null,
"session": "session_01X8U3asekbiC7yWoEPWR4Dg (subagent; parent's id, as named in claim 5833663897)",
"premise_still_valid": true,
"summary": "Card premise holds (crm_contact still carries five flat mailing_* text fields on origin/main c881114; account/lead use Field.address). Ruling precondition 1 PASSES: on pinned @objectstack/console 17.4.0 a Field.address value renders as one unit on the contact detail page (#664 has expired). PM mechanism assumption A1 is FALSIFIED: the 17.4.0 importer cannot write a compound field's parts from separate CSV columns. A mapping targeting mailing_address.street / .city / .state / .postalCode / .country fails every row of the unchanged contacts.csv on commit (50/50 INVALID_FIELD: Unknown field 'mailing_address.street' on object 'crm_contact'), while the same request as dryRun reports 44 created. Per the dispatch (no workaround, it becomes an upstream card) and AGENTS.md section 2 (no landing the half we can land now), I stopped: no code commit, no PR. Ruling item 3 (template keeps five columns, mapped into the parts) cannot be honoured on 17.4.0, so the conversion waits on an upstream importer capability; the seat files the platform card and this one takes Blocked-by.",
"ruling_quoted": [
"1. Precondition: re-measure #664 on the pinned @objectstack/console - a Field.address() value must render as a unit on the contact detail page. If it does not, stop and report; the conversion waits on the renderer, not on a flat-field workaround.",
"2. crm_contact declares mailing_address: Field.address({ ... }); the five mailing_* fields retire in the same change (ADR-0049 shape, with the app's own retirement discipline).",
"3. Import contract: assets/import-templates/contacts.csv keeps its five customer-facing columns; contact_import.mapping.ts maps them into the compound field's street / city / state / postalCode / country parts. The customer's template does not change.",
"4. The #1827 form section renders the one field; labels collapse from five rows per locale to one; content/docs/sales/contacts.mdx and its .zh-Hans / .zh-Hant siblings say one structured field; existing rows are converted once by a one-off system write.",
"5. The reason is recorded in contact.object.ts in one sentence, so the next reader sees an authored decision."
],
"measurement_664_browser": "PASS on @objectstack/console 17.4.0 (rest 17.4.0). Experimental tree: contact.object.ts with mailing_address: Field.address({ label: 'Mailing Address', group: 'mailing_address' }), view section fields [{ field: 'mailing_address', span: 'full' }]. Contact inserted via POST /api/v1/data/crm_contact/import format json with a nested mailing_address object (1/1 created; read-back returns the object with street/city/state/postalCode/country). Headless Chromium (/opt/pw-browsers/chromium) at /console/apps/app.objectstack.hotcrm/crm_contact/record/ID: the Mailing Address section shows one field MAILING ADDRESS = '12 Harbour Way, Austin, TX 78701, USA'; probe RAW_JSON_PRESENT false (no '"street":' and no '[object Object]' in body text). Screenshot kept in the session scratchpad.",
"measurement_import_unchanged_csv": "A1 FALSIFIED on 17.4.0. Fresh dev DB, accounts.csv imported first via crm_account_import (50/50 ok). Then the UNCHANGED contacts.csv via POST /api/v1/data/crm_contact/import {format:csv, mappingName:crm_contact_import} with targets mailing_address.street/.city/.state/.postalCode/.country: commit => total 50, ok 0, errors 50, every row code INVALID_FIELD 'Unknown field mailing_address.street on object crm_contact'. dryRun:true on the same body => ok 44, errors 6 (only the HR department rows), i.e. the dry run predicts success the commit refuses. Mechanism (read in node_modules/@objectstack/rest/dist/index.js, src/import-mapping.ts applyMappingToRows): transforms none/lookup/constant/map/split/join each assign mapped[target] as one flat key; nothing composes a nested object, and coerceRow passes a key with no field meta through untouched, so the engine sees a literal 'mailing_address.street' key. join into mailing_address would produce a string, which account_import.mapping.ts already records as rejected per row ('expected object, received string', objectstack#4633). pnpm validate accepted the dotted targets with no diagnostic (exit 0). Control: the engine itself accepts the compound value (nested JSON row, 1/1 created), so the gap is the mapping layer only. Baseline on main's flat mapping, same CSV: 44 created, 6 failed (HR, see findings), mailing_street..mailing_country populated.",
"measurement_conversion": "NOT MEASURED: one-off conversion script, reason: stopped at A1 before implementation; no script written.",
"tests": "NOT MEASURED: verify chain (validate, typecheck, lint, lint:i18n-gate, hygiene, hygiene:tokens, build, test), token ratchet before/after, lint field-no-consumers before/after - reason: no code change to verify (blocked at A1; tree restored, git diff HEAD empty). Only run: pnpm validate on the experimental tree, exit 0 (warnings for the orphaned mailing* translation keys; none for the dotted mapping targets).",
"files_changed": [],
"deviations": [
"Empty branch claude/issue-1836-contact-mailing-address pushed at c881114 (== origin/main at dispatch) as the rule-1 write probe; left in place as the claim's landing marker, carries no commits.",
"The baseline import (flat mapping) ran against a server that was a reload of the first dev instance, not a fresh DB (the first objectstack dev watcher's child survived my kill of the npx PID and hot-reloaded after I restored the tree; accounts therefore read 'updated 50'). Metadata was baseline (flat mailing_* populated on read-back); verdict unaffected. All dev-server PIDs I started were killed by PID afterwards.",
"No PR opened, so no label-write and no Fixes line; the claim's file surface was not touched in any commit."
],
"mcp_calls": "0",
"api_writes": "2 - git push of the empty branch (not REST); POST /repos//issues/1836/comments (this os-dev-report, via the fleet-write relay)",
"open_questions": [
{
"question": "Ruling item 3 needs the platform importer to compose a compound (address) value from separate CSV columns; 17.4.0 cannot. How does #1836 proceed?",
"options": [
"A: file the upstream card (objectstack: mapping fieldMapping target into a compound field's part, e.g. dotted target or a compose transform, plus the dry-run/commit disagreement), put Blocked-by on #1836, resume the original ruling once the pinned @objectstack/* carries it",
"B: maintainer revisits item 3 (template changes, or address columns dropped from the contact template like accounts)",
"C: land items 1,2,4,5 now and leave import broken - excluded by AGENTS.md section 2 and the dispatch"
],
"recommendation": "A, because the ruling is explicit that the customer template does not change and AGENTS.md section 2 forbids landing the half; the engine already accepts the compound value (measured), so the gap is narrow and upstream-shaped."
}
],
"out_of_scope_findings": [
"class: a - reach: public door POST /api/v1/data/crm_contact/import with mappingName crm_contact_import and the shipped assets/import-templates/contacts.csv on main: 6 of 50 rows fail invalid_option 'Department: "HR" is not a known option' (rows 7,15,23,31,39,47; option is value hr / label Human Resources; importer matches value exactly or label case-insensitively) - the app's own customer template is rejected by its own picklist; repo hotcrm; dedupe words: contacts.csv HR department invalid_option import template",
"class: a (platform, for the upstream card) - reach: same public door, dryRun:true with a dotted fieldMapping target reports ok 44 while the commit fails 50/50 INVALID_FIELD; Seam: spec:defineMapping fieldMapping[].target -> runtime:@objectstack/rest import-mapping.ts applyMappingToRows / engine insert; the dry run does not predict the commit verdict (sibling of objectstack#4633); dedupe words: import dry run INVALID_FIELD dotted target compound address mapping",
"class: c (platform) - reach: named producer contact_import.mapping.ts experimental target 'mailing_address.street' - pnpm validate (objectstack validate 17.4.0) exit 0 with no diagnostic for a fieldMapping target that names no field, which the runtime rejects on every row; dedupe words: mapping target unknown field validate lint fieldMapping",
"carrier: docs/requirements/0004-contact-buying-centre-map.md line 33 names the 'structured mailing_* block' - whoever lands the #1836 conversion; noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsRelease: session
session_01X8U3asekbiC7yWoEPWR4Dg· reason: the dev stopped correctly at the ruling's import contract (report 5833767403) · destination:pm:blocked,Blocked-by: objectstack-ai/objectstack#20149, unassigned · 2026-09-25T14:10ZWhy this waits on the platform (measured on 17.4.0)
- Ruling precondition 1 passes: a
Field.address()value renders as one unit on the contact detail page, so Address fields render as raw JSON on the record detail page #664 no longer applies. - Ruling item 3 cannot be met: the 17.4.0 importer writes each mapped column to one flat key. The unchanged
contacts.csvmapped intomailing_address.street….countryfails 50/50 on commit (INVALID_FIELD), whiledryRunreports 44 ok. The engine does accept the compound value itself (a nested JSON row imports and reads back), so only the mapping layer is missing. - ⛔ No half landing (AGENTS.md §2): converting the field now would break the customer template the ruling says must not change.
Filed upstream as objectstack-ai/objectstack#20149 (capability, plus the dry-run≠commit and validate-silence observations). The ruling stands unchanged and resumes as written once the pinned
@objectstack/*carries it.Also recorded by the dev, not filed here: the shipped
contacts.csvhas 6 of 50 rows rejected today (Department: "HR" is not a known option; the option's value ishr, label "Human Resources"). This is onmainindependent of this card and goes to the maintainer.- Ruling precondition 1 passes: a
- added and removedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatch
on Sep 25, 2026 - addedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatchand removed
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorMore actionsClaim: PM loop round R72
Session:session_01ER8ntXZhYebyQ66aXWdjfT
Account:hotlong(the seat's linked user asGET /useranswers it; always the card's assignee)
Branch:claude/issue-1836-contact-mailing-address-r72(new; the R64 branch carried no commit)
Worktree:hotcrm-issue-1836
Domain:repo:hotcrm(single-lane repo, nodomain:*taxonomy)
Seat:repo:hotcrm#1
File surface:src/sales/objects/contact.object.ts(pluscontact.hook.tsonly if it readsmailing_*),src/sales/mappings/contact_import.mapping.ts,src/sales/views/contact.view.ts,src/sales/translations/{en,zh-CN,es-ES,ja-JP}/objects.customer.ts, contact seeds undersrc/sales/data/if they carrymailing_*, onescripts/backfill-contact-mailing-address.tsin the existingscripts/backfill-*.tsshape,content/docs/sales/contacts{,.zh-Hans,.zh-Hant}.mdx, the affected tests, one.changeset/. ⛔assets/import-templates/contacts.csvdoes NOT change (ruling item 3) (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default(dispatch-gates --tier --repo objectstack-ai/hotcrmanswers "no path-derived mandate … the tier stays the PM's per-card judgment call"; a data-model conversion under a ruling)
Clause-②: no. This is app metadata oncrm_contactand converges on the shapecrm_accountandcrm_leadalready use; it touches no published schema and no accept set.
Thread-read: 5833812920
Serial constraints cleared:Blocked-by: objectstack-ai/objectstack#20149holds as consumer-installable. The issue is closedcompleted(2026-09-27). Its capability commit443b2f4f("an import mapping target may name a declared part of a compound") and the dry-run refusal7e7fab73are on objectstackmain, both dated 2026-09-27. Both predate@objectstack/spec17.5.0 (published 2026-09-29), so the installed 17.6.0 carries them. The dev re-measures this on 17.6.0 before any edit.- No open PR exists in hotcrm, and nothing else is in flight.
The ruling
5731202390(maintainer 「163 同意」, convert) stands unchanged and resumes as written.
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorMore actionsos-dev-report
{
"issue": 1836,
"status": "done",
"branch": "claude/issue-1836-contact-mailing-address-r72",
"pr": "#1997",
"session": "session_01ER8ntXZhYebyQ66aXWdjfT (subagent; the parent's id, the one claim 5967294072 names)",
"premise_still_valid": true,
"summary": "The ruling 5731202390 conversion is done. crm_contact declares one mailing_address: Field.address(). The five mailing_* fields are retired. contact_import.mapping.ts maps the five unchanged contacts.csv columns to mailing_address.street / .city / .state / .postalCode / .country (spelling read from the installed spec 17.6.0 ImportFieldMappingSchema.target). The form section renders the one field, each locale pack has one label row, the contacts docs in 3 locales say one structured field, and the reason is one sentence in contact.object.ts. PM hypothesis 1 (the importer writes compound parts on 17.6.0) PASSES. Measured before any commit, on a fresh 17.6.0 boot of the converted artifact: the unchanged contacts.csv gives dryRun 44 ok / 6 errors and commit 44 ok / 6 errors, the same as main, and two contacts read back their parts over REST. Hypothesis 2 holds: the baseline is 6 rejects on main and 6 after. scripts/backfill-contact-mailing-address.ts reads the five orphaned columns that an unprojected REST query still returns after the upgrade. It writes only empty mailing_address values, keeps an address someone set after the upgrade, never touches the old columns, and is idempotent. It was tested on a DB seeded from main's artifact: 43 converted, 1 kept, 9 left empty, second pass 0. Draft PR 1997 is open and assigned to hotlong.",
"ruling_quoted": [
"1. Precondition: re-measure #664 on the pinned @objectstack/console - a Field.address() value must render as a unit on the contact detail page.",
"2. crm_contact declares mailing_address: Field.address({ ... }); the five mailing_* fields retire in the same change.",
"3. Import contract: assets/import-templates/contacts.csv keeps its five customer-facing columns; contact_import.mapping.ts maps them into the compound field's street / city / state / postalCode / country parts. The customer's template does not change.",
"4. The #1827 form section renders the one field; labels collapse from five rows per locale to one; content/docs/sales/contacts.mdx and its .zh-Hans / .zh-Hant siblings say one structured field; existing rows are converted once by a one-off system write.",
"5. The reason is recorded in contact.object.ts in one sentence."
],
"measurement_import_h1": "PASS on 17.6.0. Every boot used objectstack dev --no-watch --no-restart --seed-admin -p 4817 with an explicit --artifact, from a cwd with no objectstack.config.ts (objectstack#21501). Served GET /api/v1/meta/object/crm_contact returned mailing fields {mailing_address: address}, and the served mapping returned the five part targets. POST /api/v1/data/crm_account/import accounts.csv 50/50 first. Then POST /api/v1/data/crm_contact/import {format csv, mappingName crm_contact_import} with the unchanged contacts.csv: dryRun total 50 / ok 44 / errors 6, commit ok 44 / errors 6. Read-back: ana.haddad → {street: 12 Harbour Way, city: Austin, state: TX, postalCode: 78701, country: USA}; ben.ibarra → {street: 480 Sequoia Ave, city: Portland, state: OR, postalCode: 97205, country: USA}. Edge rows from a scratch CSV: all five cells blank → mailing_address null; city and country only → {city: Lyon, country: France}. The same numbers were re-measured on the final-HEAD 85362cf artifact.",
"measurement_baseline_h2": "main 4807ed9 artifact, fresh DB, same door: dryRun 50 / 44 ok / 6 errors, commit 44 created / 6 errors. All six are 'Department: "HR" is not a known option'. After the conversion it is 6 errors again, the same message. Not fixed here (see findings).",
"measurement_browser": "Fresh boot of the 85362cf artifact (md5 ab0dc148...), headless Chromium via executablePath /opt/pw-browsers/chromium, port 4817. Detail page /console/apps/app.objectstack.hotcrm/crm_contact/record/ID: the Mailing Address section shows one field, MAILING ADDRESS = '12 Harbour Way, Austin, TX 78701, USA'. Probe RAW_JSON_PRESENT false. Edit (More actions → Edit) dialog, Mailing Address tab: one address widget with Street Address / City / State / Province / ZIP / Postal Code / Country, prefilled. Changed postal code 78701 → 78702 and clicked Update; the dialog closed and REST read-back gives postalCode 78702. The 44/50 import was measured on the platform import endpoint on the same boot, not through the console wizard.",
"measurement_conversion": "Fixture: main's artifact on a fresh SQLite DB, with the shipped CSVs imported (53 contacts, 44 with the five columns filled). The converted artifact was booted on a copy of that DB. After the upgrade the old columns stay physically (orphaned). An unprojected POST /query returns them, while fields: [mailing_street] answers 400 INVALID_FIELD. Before running the script: one row was edited via sqlite in the scratch copy to leave only city and country, and one row had mailing_address set over REST to stand for a post-upgrade edit. Report-only: 9 no old address, 0 converted, 1 kept, 0 unmappable, 43 to convert, nothing written. --apply: 'Converted 43/43; 0 still without their address.', exit 0. Read-back: full row has all five parts, partial row {city: Portland, country: USA}, the pre-set row kept its own value, empty rows stay null, the old columns are untouched (44 still filled). Second --apply: 43 already converted, 0 to convert, exit 0. On a fresh DB that never had the columns, it reports that nothing is visible and names the 3 reasons, exit 0.",
"tests": "pnpm verify at HEAD 85362cf under OS_VERIFY_LOCK_SLOT=hotcrm-issue-1836: 'os-verify-lock: VERDICT command-exit 0 · held the lock 180s'. Inside it: '✓ Validation passed'; tsc --noEmit clean (a separate --listFiles run counts the new script and the edited test, 2 hits); lint '1 warning(s), 18 suggestion(s)', none on crm_contact; '✓ i18n lint gate: 0 i18n/missing-* issues'; '✓ source hygiene clean'; '✓ source token ratchet clean'; '✓ Build complete'; 'Test Files 176 passed (176)', 'Tests 3787 passed | 1 skipped (3788)'. Before the test fix, the full suite on the converted tree was 2 failed (import-mappings unknown target; docs-declared-versions STATUS.md 363 vs 359), and both were fixed. Token ratchet src/sales before (4807ed9) → after: business semantics ~56,505 → ~56,418; interaction layer ~27,996 → ~27,968; authored total ~99,987 → ~99,882. No ceiling was touched. Ablation (one target mailing_address.postalCode → mailing_address.planet, via objectstack scripts/ablation-replace.mjs WRAP mode on the committed tree; anchor x1 → x0, blob ce2e9ea3274d → 88bfa04d9e50): pnpm build exit 1 '✗ Author-time rules failed' naming 'mailing_address.planet ... is not a part of the address field'; pnpm validate exit 1, same diagnostic; test/import-mappings.test.ts 1 failed / 38 passed with offender 'fieldMapping[12].target "mailing_address.planet" (unknown)'. The runtime leg inside that run was a NO-OP: the build refused, so dist kept the good artifact (marker count 0) and its 44/6 does not count. Re-measured on a copy of the built artifact with the same single edit (marker 1 in the copy, 0 in repo dist), fresh boot: dryRun 400 INVALID_FIELD 'Unknown field mailing_address.planet on object crm_contact ... refused before any row, on the dry run and the commit alike'; commit the same 400; contacts afterwards 9 (seed only). Restore (tool verdict): blob after restore ce2e9ea3274d == blob at HEAD, git diff HEAD empty; git status clean.",
"files_changed": [
".changeset/contact-mailing-address-one-field.md",
"content/docs/guides/import-and-export.mdx",
"content/docs/guides/import-and-export.zh-Hans.mdx",
"content/docs/guides/import-and-export.zh-Hant.mdx",
"content/docs/guides/importing-your-data.mdx",
"content/docs/guides/importing-your-data.zh-Hans.mdx",
"content/docs/guides/importing-your-data.zh-Hant.mdx",
"content/docs/sales/contacts.mdx",
"content/docs/sales/contacts.zh-Hans.mdx",
"content/docs/sales/contacts.zh-Hant.mdx",
"docs/STATUS.md",
"docs/requirements/0004-contact-buying-centre-map.md",
"scripts/backfill-contact-mailing-address.ts",
"src/sales/mappings/account_import.mapping.ts",
"src/sales/mappings/contact_import.mapping.ts",
"src/sales/objects/contact.object.ts",
"src/sales/translations/en/objects.customer.ts",
"src/sales/translations/es-ES/objects.customer.ts",
"src/sales/translations/ja-JP/objects.customer.ts",
"src/sales/translations/zh-CN/objects.customer.ts",
"src/sales/views/contact.view.ts",
"test/import-mappings.test.ts"
],
"deviations": [
"Files outside the claim's file surface. assets/import-templates/contacts.csv is NOT among them (git diff 4807ed9 is empty). docs/STATUS.md: the validate field count 363 → 359, which test/docs-declared-versions.test.ts pins. content/docs/guides/importing-your-data{,.zh-Hans,.zh-Hant}.mdx and import-and-export{,.zh-Hans,.zh-Hant}.mdx: they said contacts have separate address fields and that a structured address cannot be assembled from columns, and this change makes both false. account_import.mapping.ts, comment only: its last sentence relied on the contact's mailing* text fields, which is the dispatch's 'truly depends' condition. docs/requirements/0004: named the mailing_* block (the R64 carrier note). No contact seed carries mailing_*, and contact.hook.ts does not read it, so both are untouched.",
"Commit trailers are model-free ('Co-Authored-By: Claude' plus Claude-Session), per os-dev.md's trailer rule, instead of the harness reminder's model-named form. The PR body ends with os-dev.md's session-URL footer, not the harness '🤖 Generated with' form.",
"Booting: 'objectstack start' seeds no admin on an empty DB, so every measured boot used 'objectstack dev --no-watch --no-restart --seed-admin'. My first 'start' (PID 920) moved its node child 921 into its own process group, so 'kill -TERM -920' missed it. 921 kept port 4817 for about a minute, and a dev server launched then shifted to 4818. I stopped 921 and that dev group (1060) by PID. No measurement was taken on that pair. Every reported number comes from later boots through a helper that records the PID and refuses a shifted port. All servers I started are stopped.",
"The ablation's first runtime leg was a no-op (see tests). The import-endpoint refusal was measured on a hand-mutated copy of the built artifact in scratch, because the build itself refuses the mutated source.",
"The browser leg's import was measured on the platform import endpoint, not by driving the console import wizard (declared narrowing).",
"The worktree /home/user/hotcrm-issue-1836 was removed after the PR opened (node_modules first, then git worktree remove, exit 0, no --force).",
"The SubagentHandback tool is not in this run's toolset. This report is the final message and the os-dev-report comment on #1836."
],
"mcp_calls": "0",
"api_writes": "3 REST writes, each one fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): POST /repos/objectstack-ai/hotcrm/pulls (pr_create, draft, PR 1997, 11413 bytes stored identical); POST /repos//issues/1997/assignees (label-write.mjs --assign hotlong, read-back MATCHES); POST /repos//issues/1836/comments (this os-dev-report via post-stamped.mjs). Not REST: 6 git pushes (the empty branch write probe at 4807ed9, then ed53e4b, 528064c, 02e760b, b89cd1e, 85362cf). No labels were added: the dispatch named none, and skip-changeset does not apply because a changeset ships.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door POST /api/v1/data/crm_contact/import (mappingName crm_contact_import) with the shipped assets/import-templates/contacts.csv. 6 of 50 rows fail 'Department: "HR" is not a known option' (rows 7,15,23,31,39,47; the option is value hr, label Human Resources). Measured 6 on main 4807ed9 and 6 on this branch; the conversion does not change the count · repo hotcrm · dedupe words: contacts.csv HR department invalid_option import template",
"class: a · reach: named producers scripts/backfill-line-number.ts and scripts/backfill-owner-id.ts, run report-only against a fresh 17.6.0 boot: 'Backfill failed: query crm_opportunity_line_item → 400: Invalid query request' and 'Backfill failed: cannot read sys_user (400)'. The door POST /api/v1/data/OBJECT/query on 17.6.0 refuses the bodies they send: filters: [] (query.filters min_items) and sort: 'id asc' (query.sort invalid_shape). The pending .changeset/line-item-line-number-writer.md tells operators to run the first one, so the release-text exception also applies · repo hotcrm · dedupe words: backfill-line-number backfill-owner-id filters min_items sort invalid_shape query 17.6.0",
"class: a (platform) · reach: exception: security (possible data exposure) — public door POST /api/v1/data/crm_contact/query with no fields on 17.6.0 + SQLite returns orphaned columns that no metadata declares (mailing_street ... mailing_country after this upgrade). Any caller with object read can read them, outside any field-level rule, until os migrate apply --allow-destructive. Naming one in fields answers 400 INVALID_FIELD. Coupling: the #1836 backfill reads exactly this path. Closing it upstream needs a sanctioned read of orphaned columns, or the conversion has to run from a pre-upgrade export · seat routes upstream · dedupe words: orphaned column unprojected query undeclared field returned retired column REST read",
"carrier: test/import-mappings.test.ts header point 1 says a target naming no field 'is not an error at import time'. The ablation measured it false on 17.6.0 (build, validate and endpoint all refuse) · carrier: none (承接者:无) · noted in PR 1997 Acceptance notes, not filed",
"carrier: accounts.csv / leads.csv could now carry address columns mapped to billing_address / address parts, since the capability exists; nobody has asked for it · carrier: none (承接者:无) · noted in PR 1997 Acceptance notes, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorMore actionsACCEPT: PR #1997 at
85362cf0.crm_contact's mailing address becomes oneField.address(), per the ruling5731202390.repo:hotcrmseat,session_01ER8ntXZhYebyQ66aXWdjfT, 2026-10-03T09:22Z. Report: the dev'sos-dev-reporton this card (R72).Probes taken by the seat itself:
- Ancestry and checks:
main4807ed9eis an ancestor of85362cf0. CI 10/10 success. Governed check: 0 of 22 paths, NOT governed; 441 lines. - Read line by line:
contact.object.ts: onemailing_address: Field.address({ label, group: 'mailing_address' })replaces the five flat fields, and the reason is one comment.contact_import.mapping.ts: the five template columns targetmailing_address.street / .city / .state / .postalCode / .country.contact.view.ts: one full-width field in the The fivecrm_contact.mailing_*fields: the CSV importer writes them and the authored contact form may show none of them — clear the last 5field-no-consumerswarnings blocking #1581 (epic #1579) #1827 section.scripts/backfill-contact-mailing-address.ts: report-only by default; it writes only an emptymailing_addressand never overwrites; it never touches the old columns; it is rerunnable; and it uses the 17.6.0 query shapes (sortas an array, no emptyfilters).
- The customer template is untouched:
git diff origin/main…85362cf0 -- assets/is empty (ruling item 3).
Ruling items → evidence (the dev's measurements, accepted):
- Address fields render as raw JSON on the record detail page #664 has expired. On the 17.6.0 detail page the address renders as one unit (
12 Harbour Way, Austin, TX 78701, USA), with no raw JSON. The Edit dialog edits it; a postal-code change reads back over REST. - The import capability works. On a fresh 17.6.0 boot, the unchanged
contacts.csvgives a dry run of 44 ok / 6 errors and a commit of 44 / 6. That is identical tomain; the 6 are the knownDepartment: "HR"rows. Two contacts read their parts back. - The conversion works. On a DB seeded from
main's artifact: 43 converted, 1 post-upgrade edit kept, 9 empty left empty, and the second pass writes 0. - Verify and ablation:
pnpm verifyVERDICT command-exit 0(176 files). Pointing one target at a non-existent part is refused by build,validate, the test and the import endpoint (400 before any row). The restore is proven. - Tokens:
src/salesgoes down to 56,418 / 27,968 / 99,882.
Outside the claim's surface, accepted as "fix what the change makes false":
docs/STATUS.md: the field count goes 363 → 359.- The import guides in 3 locales: they had said a structured address cannot be built from columns.
- A comment in
account_import.mapping.ts. - One line in
docs/requirements/0004.
Out-of-scope findings: routed by the seat in separate cards (the
contacts.csvHR rows, the two older backfill scripts on 17.6.0, and the orphaned-column read upstream).Landing:
- Authority: ruling
5731202390(「163 同意」), and 「授权你执行pr合并」 (2026-10-03, about 04:52Z). - Path: ready → auto-merge → queue.
Generated by Claude Code
- Ancestry and checks:
- removedpm:dispatchedDispatched to a dev agent by /pm-dispatchDispatched to a dev agent by /pm-dispatch
on Oct 3, 2026
Blocked-by: objectstack-ai/objectstack#20149
Observation-class finding, noticed while working #1827 (which gave the five flat fields their missing form surface). ⛔ Deliberately not changed there — this is a data-model question with an importer contract and a migration behind it, not a form-layout fix.
The inconsistency
Three objects in this app carry a postal address, and they are modelled two different ways.
crm_accountbilling_addressField.address({ … })— one structured value withstreet/city/state/postalCode/countrypartscrm_leadaddressField.address({ … })crm_contactmailing_street·mailing_city·mailing_state·mailing_postal_code·mailing_countrytext/textareafieldsAGENTS.md's Field Type Guidance table takes a side:So the contact is the outlier against both its sibling objects and this repo's own written guidance.
Why it may nevertheless be deliberate
src/mappings/contact_import.mapping.tsstates the flat shape as a decision, in its header:and
src/mappings/account_import.mapping.tsrefers to the same split from the other side. So somebody knew. What is not on the record is the reasoning — the comment states the consequence (contacts get five CSV columns) rather than the reason, and no ADR or ruling is cited. A reader today cannot tell a considered modelling choice from an accident that was later described.There is also a plausible historical reason worth checking before anyone acts: #664 (closed) reported
Field.address()values rendering as raw JSON on the detail page. If the flat shape was chosen to dodge that renderer, the premise may have expired — that would need re-measuring against the pinned@objectstack/console@17.4.0rather than assumed in either direction.What a change would actually cost
Not a rename. Converting
crm_contacttoField.address()moves:mailing_addressfield group;assets/import-templates/contacts.csv— a customer-facing contract, five columns collapsing to one structured target;crm_contact.mailing_*fields: the CSV importer writes them and the authored contact form may show none of them — clear the last 5field-no-consumerswarnings blocking #1581 (epic #1579) #1827, and the synthesized detail screen;content/docs/sales/contacts.mdxand its.zh-Hans/.zh-Hantsiblings, which describe "five separate fields" by name;That is why this is filed rather than done, and why it wants a decision before an implementation.
What is being asked for
A ruling on which shape
crm_contactshould carry, recorded where the next reader will find it:contact.object.ts, so the deviation fromAGENTS.mdis an authored decision rather than a silent one, and consider whether the guidance table needs a caveat;Field.address()— then it is a migration card with the surfaces above enumerated, and the importer contract change needs its own call.⛔ This finding takes no side. It records that the two objects disagree, that the guidance names one of them, and that the reason is nowhere on the record.
Refs: #1827 (where it was noticed) · #664 (the
addressrenderer defect, closed).Generated by Claude Code