From 69ae02b6d6332bc26a2d2113a2ef723da952ea06 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 14:12:54 +0000 Subject: [PATCH 1/3] =?UTF-8?q?fix(spec):=20the=20mailAssociation=20livene?= =?UTF-8?q?ss=20row=20goes=20live=20=E2=80=94=20the=20mailbox=20matcher=20?= =?UTF-8?q?reads=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row was planned + authorWarn until the mailbox service's matcher existed. The matcher (service-mailbox src/ingest/match-records.ts, MailRecordMatcher) reads every email field declaring mailAssociation: true at ingest and at re-match, so the author hint ("no mailbox service reads this yet") is false. Flip to live, cite the matcher, drop authorWarn and authorHint, follow the authorWarnedProperties('field') pin and regenerate the field state counts (94 live / 4 planned -> 95 / 3). Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude --- packages/lint/src/lint-liveness-properties.test.ts | 10 +++++----- packages/spec/liveness/field.json | 10 ++++------ packages/spec/liveness/state-counts/field.md | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/lint/src/lint-liveness-properties.test.ts b/packages/lint/src/lint-liveness-properties.test.ts index 8b427ca05cd..4422725676f 100644 --- a/packages/lint/src/lint-liveness-properties.test.ts +++ b/packages/lint/src/lint-liveness-properties.test.ts @@ -1643,11 +1643,11 @@ describe('the object/field walk, against a synthetic ledger directory (#19268)', // warns on its own since #16094 — but a parse refuses it first, so it is no // subject for #11385 either. `mailAssociation` (#22870) opted in as // `planned` + `authorWarn` — the inbound-mail correspondent flag on an email - // field, declared ahead of the mailbox ingest that reads it — and it leaves - // this set when that reader lands and the row flips `live`: a verdict - // scheduled to move, so no stable subject either. The synthetic slot below - // is warned by nothing shipped. - expect([...authorWarnedProperties('field')]).toEqual(['conditionalRequired', 'mailAssociation']); + // field, declared ahead of the mailbox ingest that reads it — and left this + // set when that reader landed and the row flipped `live` (#22871): a + // verdict that moved, so it was never a stable subject either. The + // synthetic slot below is warned by nothing shipped. + expect([...authorWarnedProperties('field')]).toEqual(['conditionalRequired']); expect( lintLivenessProperties({ objects: [{ name: 'widget', fields: [{ name: 'a', synthWarnedSlot: true }] }], diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index 44bb18c2cbf..1e2c7cfd8a6 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -246,12 +246,10 @@ "note": "[#11339] Polymorphic pointer pair (ADR-0052 §5 ActivityPointer): marks a text field as the id half whose target object the SIBLING column names per row. Consumer (seed-time natural-key resolution + loud refusal of unresolvable/un-addressable pointers) lands in the same PR (ADR-0049 declare = enforce; the maskingRule precedent). Authoring contradictions refused at parse (text-only, exclusive with reference) and create (sibling must be declared). Proven in packages/metadata-protocol/src/seed-loader-pointer-pair.test.ts and packages/spec/src/data/field.test.ts." }, "mailAssociation": { - "status": "planned", - "verifiedAt": "2026-10-12", - "authorWarn": true, - "authorHint": "Keep it: no mailbox service reads this yet; it takes effect when the mailbox capability ships.", - "evidence": "packages/spec/src/data/field.zod.ts#mailAssociation (the parse-time door: the FieldSchema superRefine refuses the key on every type but `email`, beside the `referenceVia` checks); packages/spec/src/data/field-mail-association.test.ts (the accept and refuse pins)", - "note": "PLANNED — declared ahead of its one reader by the card's own sequencing (#22870 PR 1 of 2; ruling 6116489638, Q1 → A: the inbound-mail association seam is a declared field-level key, valid only on `type: 'email'`). The card's constraint: \"Declared = enforced. Every key this card adds must get its runtime reader in the sibling ingest card, or it is not added. Liveness rows start `planned` and leave it when the reader lands.\" The reader is the mailbox service's ingest and re-match (the sibling ingest card #22871, Blocked-by #22870), which matches every flagged email field in the mailbox's organization against a message's participant addresses and records one association per match. No reader in this repo today, measured at 018b077fb before this change: `git grep mailAssociation -- packages` answers 0 hits (control: `referenceVia` answers 12 in field.zod.ts alone). `authorWarn` per enforce-or-mark (the externalSharingModel / page.print shape): an author who sets it is told no mailbox service reads it yet. Flip to `live` when the ingest lands, citing its matcher symbol, and drop `authorWarn` / `authorHint` in that same edit." + "status": "live", + "verifiedAt": "2026-10-13", + "evidence": "packages/services/service-mailbox/src/ingest/match-records.ts#MailRecordMatcher (its `match`, through `flaggedFieldsOf`: every registered object's `email` fields declaring `mailAssociation: true` are the only fields a message's participant addresses are matched against, inside the mailbox's organization; each match becomes one `sys_mail_association` row at ingest and at re-match); packages/spec/src/data/field.zod.ts#mailAssociation (the parse-time door: the FieldSchema superRefine refuses the key on every type but `email`); packages/spec/src/data/field-mail-association.test.ts (the accept and refuse pins)", + "note": "LIVE — the reader is the mailbox service's ingest seam and re-match (`@objectstack/service-mailbox`, ruling 6116489638 on #22870, Q1 → A: the inbound-mail association seam is a declared field-level key, valid only on `type: 'email'`, with no resolver contract). It landed in two stages of #22871: the ingest seam and the matcher at 82581819b, then the IMAP connector (`@objectstack/connector-imap`) and the mailbox sync that fetches a mailbox's mail and hands each message to the seam. The row was `planned` + `authorWarn` from #22870 until the reader existed; the hint it carried (no mailbox service reads this yet) stopped being true when the matcher landed, so both are dropped in the edit that flips it. Takes effect only where the mailbox capability is composed (`requires: ['mailbox']`)." }, "autonumberFormat": { "status": "live", diff --git a/packages/spec/liveness/state-counts/field.md b/packages/spec/liveness/state-counts/field.md index 9cefcb90213..72f2e5392d5 100644 --- a/packages/spec/liveness/state-counts/field.md +++ b/packages/spec/liveness/state-counts/field.md @@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them. | Type | live | exp | elsewhere | dead | planned | classified | |---|---|---|---|---|---|---| -| `field` | 94 | 0 | 0 | 1 | 4 | 99 | +| `field` | 95 | 0 | 0 | 1 | 3 | 99 | From 4d4fac7339543ccf60de5c132fe61a1efbfcb3ab Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 17:15:58 +0000 Subject: [PATCH 2/3] chore(spec): changeset for the mailAssociation liveness flip (the ledger ships in the spec tarball) Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ Co-authored-by: Claude --- .changeset/22871-mail-association-live.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/22871-mail-association-live.md diff --git a/.changeset/22871-mail-association-live.md b/.changeset/22871-mail-association-live.md new file mode 100644 index 00000000000..74e3c877117 --- /dev/null +++ b/.changeset/22871-mail-association-live.md @@ -0,0 +1,9 @@ +--- +'@objectstack/spec': patch +--- + +The `mailAssociation` liveness row is `live`: the mailbox service's matcher reads the key, so authoring `mailAssociation: true` on an email field no longer draws the "no mailbox service reads this yet" author warning + +Clause-②: no (dropping an author warning widens no accept set and no export: `FieldSchema` accepts `mailAssociation` exactly as before; only the warning the shipped liveness ledger carried is gone) + +- `liveness/field.json`: the row moves from `planned` with `authorWarn` and `authorHint` to `live`, citing `MailRecordMatcher` in `@objectstack/service-mailbox` as its reader. The `field` state counts move from 94 live and 4 planned to 95 live and 3 planned. From 8ccbeda9d43563e3a31115d328f78d58622325f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 20:19:22 +0000 Subject: [PATCH 3/3] fix(spec): the mailAssociation ledger note claims only what main holds The note now says the reader is stage 1 of #22871 (the ingest seam and the matcher, landed at 82581819b) and names the IMAP connector and the mailbox sync as the card's second stage, which feeds that same seam, without claiming it has landed. status, verifiedAt and evidence are byte-identical; no PR number is named. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ --- packages/spec/liveness/field.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index 1e2c7cfd8a6..e684af33a10 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -249,7 +249,7 @@ "status": "live", "verifiedAt": "2026-10-13", "evidence": "packages/services/service-mailbox/src/ingest/match-records.ts#MailRecordMatcher (its `match`, through `flaggedFieldsOf`: every registered object's `email` fields declaring `mailAssociation: true` are the only fields a message's participant addresses are matched against, inside the mailbox's organization; each match becomes one `sys_mail_association` row at ingest and at re-match); packages/spec/src/data/field.zod.ts#mailAssociation (the parse-time door: the FieldSchema superRefine refuses the key on every type but `email`); packages/spec/src/data/field-mail-association.test.ts (the accept and refuse pins)", - "note": "LIVE — the reader is the mailbox service's ingest seam and re-match (`@objectstack/service-mailbox`, ruling 6116489638 on #22870, Q1 → A: the inbound-mail association seam is a declared field-level key, valid only on `type: 'email'`, with no resolver contract). It landed in two stages of #22871: the ingest seam and the matcher at 82581819b, then the IMAP connector (`@objectstack/connector-imap`) and the mailbox sync that fetches a mailbox's mail and hands each message to the seam. The row was `planned` + `authorWarn` from #22870 until the reader existed; the hint it carried (no mailbox service reads this yet) stopped being true when the matcher landed, so both are dropped in the edit that flips it. Takes effect only where the mailbox capability is composed (`requires: ['mailbox']`)." + "note": "LIVE — the reader is the mailbox service's ingest seam and re-match (`@objectstack/service-mailbox`, ruling 6116489638 on #22870, Q1 → A: the inbound-mail association seam is a declared field-level key, valid only on `type: 'email'`, with no resolver contract). The reader is the first of the two stages of #22871, the ingest seam and the matcher, which landed at 82581819b; the card's second stage, the IMAP connector (`@objectstack/connector-imap`) and the mailbox sync that fetches a mailbox's mail, feeds that same seam and does not change what reads this key. The row was `planned` + `authorWarn` from #22870 until the reader existed; the hint it carried (no mailbox service reads this yet) stopped being true when the matcher landed, so both are dropped in the edit that flips it. Takes effect only where the mailbox capability is composed (`requires: ['mailbox']`)." }, "autonumberFormat": { "status": "live",