Skip to content

fix(spec): the mailAssociation liveness row goes live — the mailbox matcher reads it, so the stale author warning is dropped - #23314

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-22871-s2-liveness-flip
Oct 13, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-22871-s2-liveness-flip

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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.

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


Generated by Claude Code

…atcher reads it

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 <noreply@anthropic.com>
…ger ships in the spec tarball)

Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Oct 13, 2026
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/spec/liveness/field.json, packages/spec/liveness/state-counts/field.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/liveness/field.json, packages/spec/liveness/state-counts/field.md) — pages documenting those are invisible to this run
  • 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 — 142 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 cc14657ede79cd315fa7647da238a48a6ede9b88 → packageMentionDocs.

The note now says the reader is stage 1 of #22871 (the ingest seam and
the matcher, landed at 8258181) 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8ccbeda9d43563e3a31115d328f78d58622325f1
Local-runs: none

PR #23314 (Part of #22871, draft, 4 files, +19/−12, not governed: none of .claude/**, docs/adr/**, skills/**, AGENTS.md, CLAUDE.md). Base 280806912, which is an ancestor of origin/main 14a13bce0; main has not touched packages/spec/liveness/** or the lint test since that base (only new .changeset/*.md files landed). Read: card #22871 body and all 16 comments (seat answer 6151023438, claim 6147217218, amendment 6151040021, ACCEPT 6154419315 as a claim under test), ruling 6116489638 on #22870, the PR body, its one bot comment, the file list, the net diff, PR #23303 as context, the check-runs on this head, and the heads by sha through read-only git.

① Derived judgments

  • (a) The reader is real on main alone, and live is the ledger's own word for it. packages/services/service-mailbox/src/ingest/match-records.ts at main declares export class MailRecordMatcher; its match(organizationId, addresses) calls flaggedFieldsOf(engine), which keeps exactly the fields where def.type === 'email' && def.mailAssociation === true (skipping the three mail objects and any flagged object with no organization_id column, said once) and reads them under the system context scoped to the mailbox's organization; mail-ingest-service.ts calls this.matcher.match at ingest (line 318, linksFor → one sys_mail_association row per match) and at re-match (line 426). No other path reads the flag. The reader is reachable on main today: MailboxServicePlugin.init() registers MailIngestService as the kernel service mailbox; packages/core/src/capability-providers.ts boots that plugin for every host declaring requires: ['mailbox'] (the showcase does); MailIngestService is exported from the package index; rematch is a door for the system context, a manage_mailboxes holder or the mailbox owner, and ingest for the system context — a host plugin on main can hand the seam a message now, and the stage-1 kernel suite (mailbox-service-kernel.test.ts, Field.email({ mailAssociation: true }) on a booted kernel over real ObjectQL) proves the end-to-end on main. Under the ledger's vocabulary (liveness/README.md): live = "has a runtime consumer; authoring the property changes runtime behaviour", planned = "declared, intentionally not enforced yet". The flag is enforced wherever a message reaches the seam; what main lacks is an in-repo mail source, not a reader. The producer criterion does not bite: the consumer reads the authored value directly off the registered schema (the README's "author IS the producer" row), and the addresses it compares against are derived inside ingest from the message itself. The row's own flip condition, written by spec + service-mailbox: sys_mail_message (the inbound mirror object) and the association seam — phase 1 of #8998 inbound mail, spec seat #22870's seeding, is met verbatim: "Flip to live when the ingest lands, citing its matcher symbol, and drop authorWarn / authorHint in that same edit." Keeping planned + authorWarn would ship the hint "no mailbox service reads this yet" as a false author warning (the [finding] field.relatedListFilter 的 liveness 行仍是 planned + authorWarn,而它自己写明的 flip 条件早已落地 —— os lint 正在用一句假话把作者劝离一个可用的键 #19187 lesson recorded in this same file at the relatedListFilter row), and check:liveness refuses authorWarn on a live row, so the flip must drop both — it does. The stage-1 dev's reservation (report 6145035987: "the author-visible effect is still absent") conflates a missing source with a missing reader; the ledger grades readers.
  • (b) The row. status: live; verifiedAt: 2026-10-13 (well-formed, not future-dated); evidence carries three repo-local citations and every one resolves on the head: match-records.ts#MailRecordMatcher (one identifier, the form check:liveness accepts — a dotted #MailRecordMatcher.match fails its [A-Za-z_$][A-Za-z0-9_$]* rule, so citing the class and naming match → flaggedFieldsOf in the prose is the right spelling; the file names the key 4 times), field.zod.ts#mailAssociation (line 1504 declares it, the superRefine at line 2253 refuses it off type: 'email'), and field-mail-association.test.ts (exists, 27 key mentions). The key-mention check is satisfied for each cited file. The Spec property liveness check-run (which runs check:liveness: anchors, key mention, verifiedAt, the warned-row census and the state-counts freshness leg) is green on this head. The evidence prose is accurate: the flagged email fields are the only fields matched against, inside the organization, one sys_mail_association row per match at ingest and at re-match. The trimmed note (commit 8ccbeda9d, the only change since 4d4fac733, one line): every sentence holds on main with this PR alone — the ruling citation (Q1 → A, field-level key valid only on type: 'email', no resolver contract) matches 6116489638; "the first of the two stages of connector-imap + minimal service-mailbox: shared-mailbox (forward/BCC) ingest into sys_mail_message, associated at ingest through the seam — phase 1 of #8998 inbound mail #22871, the ingest seam and the matcher, which landed at 8258181" is true (82581819b is an ancestor of the base); the second stage is named as the card's stage that feeds the seam "and does not change what reads this key" without claiming it landed; the hint's history is stated correctly; "takes effect only where the mailbox capability is composed" is a true necessary condition. The replaced clause ("It landed in two stages … then the IMAP connector … and the mailbox sync") would have been false on main and is gone.
  • (c) Derived changes are exact. authorWarnedProperties('field') → ['conditionalRequired']: that row is dead (warns by verdict, no marker), and after this edit no field.json row carries authorWarn at any depth — the function is ledger-driven (packages/lint/src/lint-liveness-properties.ts, un-memoised), so the pin follows the row, and the test's comment now records the flip under connector-imap + minimal service-mailbox: shared-mailbox (forward/BCC) ingest into sys_mail_message, associated at ingest through the seam — phase 1 of #8998 inbound mail #22871. state-counts/field.md: 94 → 95 live, 4 → 3 planned, 99 classified unchanged; at the gate's leaf granularity main's four planned are settledWhen, conditionalFormatting.condition, conditionalFormatting.style, mailAssociation, and the head's three drop the last — consistent, and the gate's byte-compare freshness leg is green on the head. No other count site exists: the README state table carries no count columns since spec(liveness): 9 rows of the README state table have count columns that disagree with the gate's --json report #7377 (no 94 or 4 planned anywhere else on main), llms.txt carries no liveness counts, the docs (content/docs/references/data/field.mdx, object.mdx, system/migration.mdx) and the Studio helpText describe the key conditionally ("when a mailbox service ingests a message") with no status claim, and no test other than the pin asserts the planned warning for mailAssociation. The docs-drift bot's "2 files yielded no anchor" is the ledger and shard, which are not documentable surfaces; nothing restating the row's status exists to move.
  • (d) Changeset .changeset/22871-mail-association-live.md. Owed: liveness/ ships in @objectstack/spec's files, and os lint / os validate read it through authoring-rules.ts → lintLivenessProperties, so a consumer-visible warning disappears — never skip-changeset. patch is the right level: a false author warning is a defect, nothing is added; Clause-②: no is right: the PR touches no .zod.ts, FieldSchema's refine accepts and refuses exactly as before, no export, key or default moves, and a warning narrowing widens no accept set. The ADR-0087 gate's own accepted shape is a bare Clause-②: no on '@objectstack/spec': patch; precedent on main's CHANGELOG: 17.7.0 patch "the flows row drops authorWarn and its authorHint … no longer warn liveness-planned-property", and 18.0.0-next patch flipping the requiredPermissions rows live. Pre mode (.changeset/pre.json = pre / next): a patch moves the fixed group (71 packages, spec and service-mailbox among them) one -next.N — right; no second package line is owed, service-mailbox is unchanged. Every sentence true on main alone: the matcher reads the key; the warning no longer fires; the row moves as stated, citing MailRecordMatcher; the counts move as stated. No tracker number in the author-facing body. Check Changeset is green.
  • (e) Interaction with PR feat(connector-imap, service-mailbox): the IMAP mail source — a read-only IMAP connector and the mailbox sync that feeds the ingest seam #23303 (head 5e075ec2f, still carrying 610e112bd): compared patch for patch, lint-liveness-properties.test.ts and state-counts/field.md are byte-identical in both PRs, and field.json differs on exactly one line, the note (status, verifiedAt, evidence identical). Whichever lands second meets a conflict on that one line only (the identical lines merge clean); resolving to main's side is correct in either order — the trimmed note claims only what main holds and stays true after feat(connector-imap, service-mailbox): the IMAP mail source — a read-only IMAP connector and the mailbox sync that feeds the ingest seam #23303 lands, while the untrimmed note's landing claim is only true once feat(connector-imap, service-mailbox): the IMAP mail source — a read-only IMAP connector and the mailbox sync that feeds the ingest seam #23303 is in. feat(connector-imap, service-mailbox): the IMAP mail source — a read-only IMAP connector and the mailbox sync that feeds the ingest seam #23303's changeset no longer lists @objectstack/spec, so the flip is released once. The cherry-pick is exact: 69ae02b6d's change lines equal 610e112bd's, diff for diff.
  • (f) CI on 8ccbeda9d: 41 check-runs, 35 success, 0 failure, 0 pending. Non-green = 6 skips, every one on EXPECTED_SKIPS (scripts/pm/check-expected-skips.mjs): Build Docs, Console Pin Gate, the Dogfood Regression Gate matrix-shard job, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL). Spec property liveness, Check Changeset, Governed Surface Queue Guard, Part-of PR must not also close its card, all six Test Core shards and every Lint and Type Check job are green. No legacy commit statuses.

② Semver level

'@objectstack/spec': patch, Clause-②: no — correct. Pre mode next; one fixed-group pre-release bump. No minor is owed (nothing widens) and no breaking arm exists (nothing narrows the accept set or removes anything authorable).

③ Boundary flags

  • Non-blocking, wording: the note's "the card's second stage … feeds that same seam" is present tense about a stage that has not landed; it reads as the stage's design, and the commit message says no landing is claimed, but "is to feed" would close the reading. The note is maintainer prose never shown to an author.
  • Non-blocking, measured: on main no code outside service-mailbox calls the mailbox service (0 hits), so until feat(connector-imap, service-mailbox): the IMAP mail source — a read-only IMAP connector and the mailbox sync that feeds the ingest seam #23303 lands the only producer of messages to the seam is a host's own call. The row's "takes effect only where the mailbox capability is composed" is a necessary condition, not a sufficient one; live is still the right grade because the ledger grades readers, and the reader is wired and reachable.
  • Non-blocking: the row carries no evidenceScope; absent is a worklist state, not a failure. in-repo would be accurate.
  • Non-blocking: the API still reports mergeable: unknown for this draft at read time (lazily computed; a merge_commit_sha exists). main has not touched any of the four paths since the base, so a clean merge is expected; the seat confirms mergeable: clean at the pre-queue check as usual.
  • Not this PR's: main's .changeset/22871-mail-ingest-seam.md still says "no mail source feeds the seam", which remains true until feat(connector-imap, service-mailbox): the IMAP mail source — a read-only IMAP connector and the mailbox sync that feeds the ingest seam #23303 lands and is superseded by that PR's changeset.

Implemented-by: claude/issue-22871-s2-liveness-flip
Reviewed-by: session_012uFegdfR6K6uqvLzt7XsdZ

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 13, 2026 21:32
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit 777eed2 Oct 13, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22871-s2-liveness-flip branch October 13, 2026 22:02
os-support-ai pushed a commit that referenced this pull request Oct 13, 2026
#23314 landed the mailAssociation liveness flip as 777eed2 with the
trimmed note; the one conflicting line, that row's note in
packages/spec/liveness/field.json, is resolved to main's side, so this
branch's flip commit is a no-op against main.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ
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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants