Skip to content

Commit 777eed2

Browse files
fix(spec): the mailAssociation liveness row goes live — the mailbox matcher reads it, so the stale author warning is dropped (#23314)
Part of #22871 Clause-②: no (dropping an author warning widens no accept set and no published surface: `FieldSchema` accepts `mailAssociation` exactly as before, and no export, key or default changes; the shipped liveness ledger loses one `authorWarn` row, so `os lint` stops warning on the key) ## The `mailAssociation` liveness row goes `live`, on its own PR This is stage 2's carried item 1 on #22871, split off under the seat's answer `6151023438` and claim amendment `6151040021`. - It carries exactly commit `610e112bd` from PR #23303, cherry-picked onto `main` at `280806912`: `packages/spec/liveness/field.json`, `packages/spec/liveness/state-counts/field.md` and `packages/lint/src/lint-liveness-properties.test.ts`. The cherry-picked change lines are identical to the original's (compared as diffs). - It adds one changeset, because `liveness/` ships in the `@objectstack/spec` tarball (`files`). - **Why now:** the row's author hint, "no mailbox service reads this yet", has been false on `main` since the stage-1 matcher landed at `82581819b`. PR #23303 (3690 lines) waits for a human merge, and this hint must not ship in a release. ### The change - **`field.json`:** `mailAssociation` moves from `planned` with `authorWarn` and `authorHint` to `live`. - Its evidence is `packages/services/service-mailbox/src/ingest/match-records.ts#MailRecordMatcher`, on `main` since `82581819b`. Its `match` reads every `email` field declaring `mailAssociation: true`, through `flaggedFieldsOf`. - The anchor is the class, not `#MailRecordMatcher.match`, because `check:liveness` refuses a dotted anchor. - **The pin:** `authorWarnedProperties('field')` becomes `['conditionalRequired']`. - **`state-counts/field.md`:** 94 live and 4 planned become 95 live and 3 planned. The file is regenerated by `gen:liveness-counts`. ### Why `Clause-②: no` The declaration asks whether the change widens an accept set or the public surface. - `FieldSchema`'s refine is unchanged: the key was accepted on `email` and refused elsewhere before this PR, and it still is. - No export, key or default moves. - What changes is one ledger row that `os lint` reads, so an author who writes the key stops being warned that nothing reads it. That narrows a warning; it accepts nothing new. - So the changeset is `'@objectstack/spec': patch` with `Clause-②: no`. ### Verification at the PR head All of this ran at `4d4fac733`, on `main` `280806912`, under `os-verify-lock` (one call, `VERDICT command-exit 0`): - a full `pnpm turbo run build --concurrency=2 --filter=!@objectstack/docs` (74 of 74 tasks); - `pnpm --filter @objectstack/spec check:liveness`, which exits 0. Every `path#symbol` anchor resolves, and `state-counts/` is current; - `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/lint-liveness-properties.test.ts`: 100 passed; - `pnpm --filter @objectstack/spec test:repo`: 58 files, 1007 passed. **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 71 families. All 71 were run, one log each, with exit codes captured before any pipe, and `--ran` reconciles: "71 derived, 71 run, 0 NOT-MEASURED, 0 UNRUN". - 70 exit 0, among them `check:adr-0087-registration`, `check:changeset-no-major`, `check:empty-changeset`, `check:generated`, `check:liveness` and `check:published-files`. - `pnpm check:platform-checklist` exits 1, with the same 8 findings `main` shows. This diff touches none of its inputs (`docs/qa/**`, `metadata-protocol`, `service-storage`, the anchor scripts), so the red is `main`'s. ### Notes - PR #23303 keeps commit `610e112bd`. Once this lands, merging `main` into it is a no-op for these three files. - The row's `note` says the reader landed "in two stages of #22871 … then the IMAP connector … and the mailbox sync". The second half is true only once PR #23303 lands. This PR carries the commit exactly, as ordered; the seat decides whether that sentence is trimmed here. --- _Generated by [Claude Code](https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 614fdec commit 777eed2

4 files changed

Lines changed: 19 additions & 12 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
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
6+
7+
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)
8+
9+
- `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.

‎packages/lint/src/lint-liveness-properties.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,11 +1643,11 @@ describe('the object/field walk, against a synthetic ledger directory (#19268)',
16431643
// warns on its own since #16094 — but a parse refuses it first, so it is no
16441644
// subject for #11385 either. `mailAssociation` (#22870) opted in as
16451645
// `planned` + `authorWarn` — the inbound-mail correspondent flag on an email
1646-
// field, declared ahead of the mailbox ingest that reads it — and it leaves
1647-
// this set when that reader lands and the row flips `live`: a verdict
1648-
// scheduled to move, so no stable subject either. The synthetic slot below
1649-
// is warned by nothing shipped.
1650-
expect([...authorWarnedProperties('field')]).toEqual(['conditionalRequired', 'mailAssociation']);
1646+
// field, declared ahead of the mailbox ingest that reads it — and left this
1647+
// set when that reader landed and the row flipped `live` (#22871): a
1648+
// verdict that moved, so it was never a stable subject either. The
1649+
// synthetic slot below is warned by nothing shipped.
1650+
expect([...authorWarnedProperties('field')]).toEqual(['conditionalRequired']);
16511651
expect(
16521652
lintLivenessProperties({
16531653
objects: [{ name: 'widget', fields: [{ name: 'a', synthWarnedSlot: true }] }],

‎packages/spec/liveness/field.json‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,10 @@
246246
"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."
247247
},
248248
"mailAssociation": {
249-
"status": "planned",
250-
"verifiedAt": "2026-10-12",
251-
"authorWarn": true,
252-
"authorHint": "Keep it: no mailbox service reads this yet; it takes effect when the mailbox capability ships.",
253-
"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)",
254-
"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."
249+
"status": "live",
250+
"verifiedAt": "2026-10-13",
251+
"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)",
252+
"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']`)."
255253
},
256254
"autonumberFormat": {
257255
"status": "live",

‎packages/spec/liveness/state-counts/field.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them.
1212

1313
| Type | live | exp | elsewhere | dead | planned | classified |
1414
|---|---|---|---|---|---|---|
15-
| `field` | 94 | 0 | 0 | 1 | 4 | 99 |
15+
| `field` | 95 | 0 | 0 | 1 | 3 | 99 |

0 commit comments

Comments
 (0)