Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/22871-mail-association-live.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 5 additions & 5 deletions packages/lint/src/lint-liveness-properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }] }],
Expand Down
10 changes: 4 additions & 6 deletions packages/spec/liveness/field.json
Original file line number Diff line number Diff line change
Expand Up @@ -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). 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",
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/liveness/state-counts/field.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Loading