Skip to content

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

Closed

Description

@objectstack-fleet

Filing gate: ④ — a per-layer child of the coordination node #8998. Part of #8998.

Blocked-by: #22870

Provenance: same as #22870.

This card carries phase 1's ingest half. It waits on #22870, which publishes the object (sys_mail_message) and the association seam this card writes to and calls.

Scope

Taken from the design record. ⛔ Nothing here re-decides it.

  • packages/connectors/connector-imap: protocol and auth only, following the existing connector convention (connector-rest, connector-openapi, connector-mcp, connector-slack). The account + credential abstraction is shaped for the per-user mailbox now. The shared mailbox is its first implementor (decision 4), so per-user sync later extends it rather than rewriting it.

  • packages/services/service-mailbox, minimal:

    • folder cursor and UID state;
    • incremental fetch, dedupe and threading;
    • attachment extraction;
    • bounce and auto-reply suppression.

    It writes sys_mail_message, calls the seam at ingest and stamps the association. Unmatched messages go to the pool, and the explicit re-match action retro-matches them (decisions 2 and 3).

  • Phase-1 privacy semantic: "forwarding is consent" (decision 5).

  • ⛔ Out of scope, as chained follow-ups filed once phase 1 proves out:

    • per-user IMAP sync;
    • CalDAV and calendar;
    • write-back;
    • the enterprise admin plane (tenant-wide delegation, bulk provisioning, sync health).

Why one card covers two packages: phase 1's connector has exactly one consumer (this service), and neither can be proven end to end alone. The lane may split it at claim time through pm:retriage.

Acceptance

An end-to-end test against an in-process IMAP server double:

  • A forwarded message ingested from the shared mailbox lands as one sys_mail_message row, with the association stamped by an app-supplied resolver.
  • A second fetch dedupes, and the row count stays 1.
  • An unmatched message lands in the pool, and the re-match action associates it once a matching record exists.
  • An auto-reply is suppressed.

Each clause has a negative control that fails when the behaviour is removed.

Who acts

The domain:services seat, size L, once #22870 lands. Clause-②: yes, because it adds two published packages.

Dedup

Same queries and readings as #22870. No mail-ingest, IMAP or calendar card exists besides #8998.

Activity

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#22871, stage 1 of triage 6137138392: the ingest seam in service-mailbox, Part of #22871) · 2026-10-13T09:10Z
    Session: session_01BRQ9Mp1yH7R1az6MaUM18H
    Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22871-s1-ingest-seam
    Worktree: objectstack-issue-22871-s1
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    Ruling-ref: 6116489638 on #22870 (batch #319 item 2: Q1 A, Q3 C staged, Q4 A; Q2 C3 and Q5 A stand), recorded on this card in 6116501990; triage 6137138392 (the spec lane claims; one card, two staged PRs). Both read in this act.
    File surface (at origin/main 150da61aa or later; stop on breach and explain in the report):

    This act moves the card pm:queue → pm:dispatched and assigns os-justin. In the same act this seat files the chained inline-forward follow-up that 6116501990 orders, Blocked-by: #22871.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22871,
     "status": "done",
     "branch": "claude/issue-22871-s1-ingest-seam",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23250",
     "session": "session_01BRQ9Mp1yH7R1az6MaUM18H — subagent run: the parent PM's session id, as the dispatch and every commit's Claude-Session trailer carry it",
     "premise_still_valid": true,
     "summary": "Stage 1 of #22871 lands the inbound-mail ingest seam in packages/services/service-mailbox/src/ingest/. MailIngestService is registered by MailboxServicePlugin.init() as the kernel service `mailbox` and exported. It has two entry points. ingest({mailboxId, raw, receivedAt?}, ctx) is system-only. It parses one raw RFC 822 message with postal-mime@^4.0.5, the one new third-party dependency (MIT-0, 0 deps, ESM and CJS). It reads the participants under source `outer` and the same five headers of a top-level message/rfc822 part under source `attachment`, and leaves the mailbox's own address out. It suppresses Auto-Submitted, Precedence bulk/junk/list, X-Autoreply/X-Autorespond and multipart/report delivery-status by writing no row, because association_state declares no suppressed state. It dedupes on mid:/hdr: SHA-256 keys, with the unique index settling a race. It matches every `email` field declaring mailAssociation: true inside the mailbox's organization, ASCII case-insensitive and exact. It then writes the message unassociated, one sys_mail_association per match (matched_by ingest), and the flip to associated. rematch({mailboxId, messageIds?}, ctx) replays the stored match_addresses and only adds links (matched_by rematch). It is open to the system context, and to a manage_mailboxes holder or the mailbox owner in the mailbox's organization; anyone else gets 403 PERMISSION_DENIED, and another organization's mailbox answers 404 RECORD_NOT_FOUND. No resolver contract exists. Zone 2 measured: every #22870 input is present and none was changed; 0 in-tree objects flag mailAssociation (census in the PR body); attachments are not stored because nothing server-side writes sys_attachment (deviation below).",
     "tests": "At 0324d20f2: `pnpm --filter @objectstack/service-mailbox test` gives 6 files and 64 tests passed. `typecheck` (tsc --noEmit plus the scripts program) exits 0, and tsc --listFiles counts the 3 ingest test files. The engine suite runs on a real ObjectQL over driver-sql and better-sqlite3 :memory: with the real three objects and unique indexes. A kernel suite boots a real ObjectKernel with ObjectQLPlugin and MailboxServicePlugin and ingests through getService('mailbox'). Build: `pnpm turbo run build --filter=@objectstack/service-mailbox... --concurrency=2` ran 16/16 tasks green at ae14c39c5; the package build emitted ESM, CJS and DTS (check-dts-emitted 2/2). A probe require()d dist/index.cjs and ingested a message: encoded subject, display name and participant set all correct. Lint, narrowed: `eslint --no-inline-config --format json` over the 16 changed .ts files at 0324d20f2 gives 16 files, 0 errors, 0 warnings, 0 fatal. The population is eslint.config.mjs files '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}', and the rest of the diff is md/mdx/json/yaml. The count is read from the JSON. Invariance: the config enables no type-aware linting (no parserOptions.project), so no untouched file's verdict can move. Ablation, one-off: 15 mutations through scripts/ablation-replace.mjs. Each anchor hit x1, each blob change was proven on disk, each file was restored to blob == HEAD with `git diff HEAD` empty, and the tests ran against mutated source (relative imports, no dist). Red when removed: A1 dedupe key (5 tests), A2b DUPLICATE_RECORD catch (1), A3 pool state (1), A4 rematch link (2), A5 suppression return (2), A6 bounce signal (2), A7 attachment source (1), A8 org filter (1), A9b substring matching (1), A10 own-address exclusion (3), A11 ingest system-only (1), A12 rematch org scope (1), A13 rematch authority (1). Green as expected: A2 pre-read removed (the unique key holds) and A9 in-chunk filter alone (exactness is also held by linksFor's keyed lookup). A1's first attempt was refused by the tool as a no-op (the replacement held the anchor) and was re-anchored. A1-A6 ran at 451638f33; A2b, A7-A13 and A9b ran at a869f533e.",
     "mcp_calls": "0",
     "api_writes": "2 — (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204). Relay run 38393756273 executed POST /repos/objectstack-ai/objectstack/pulls and POST /repos/objectstack-ai/objectstack/issues/23250/assignees, and the read-back was identical (18601 bytes, assignee os-justin). (2) This os-dev-report comment through scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/22871/comments (relay). 0 label writes: the diff publishes and carries a minor changeset, so skip-changeset is not owed, and the dispatch names no other label. git push of the branch is not a REST write.",
     "gates": [
      "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
      "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
      "node scripts/check-changeset-fixed.mjs :: exit 0",
      "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
      "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
      "node scripts/check-ci-filter-parity.mjs :: exit 0",
      "node scripts/check-closing-keyword-parity.mjs :: exit 0",
      "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
      "node scripts/check-comment-mask-adoption.mjs :: exit 0",
      "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
      "node scripts/check-comment-mask-corpus.mjs :: exit 0",
      "node scripts/check-doc-frontmatter.mjs :: exit 0",
      "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
      "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
      "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
      "node scripts/check-docs-section-name.mjs :: exit 0",
      "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
      "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
      "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
      "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
      "node scripts/check-issue-citations.mjs :: exit 0",
      "node scripts/check-keyed-text-bounds.mjs :: exit 0",
      "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
      "node scripts/check-osv-exemptions.mjs :: exit 0",
      "node scripts/check-osv-exemptions.mjs --self-test :: exit 0",
      "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
      "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
      "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
      "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
      "node scripts/check-registry-log-declared.mjs :: exit 0",
      "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
      "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
      "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
      "node scripts/check-section-landing-index.mjs :: exit 0",
      "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
      "node scripts/check-system-context-census.mjs :: exit 0",
      "node scripts/check-system-context-census.mjs --self-test :: exit 0",
      "node scripts/check-tenant-audit-census.mjs :: exit 0",
      "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
      "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
      "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
      "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
      "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
      "node scripts/osv-base-relative.mjs --self-test :: exit 0",
      "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
      "node scripts/release-pending-publish.mjs --self-test :: exit 0",
      "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
      "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
      "pnpm --filter @objectstack/spec run check:docs :: exit 0",
      "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
      "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
      "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
      "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
      "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
      "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
      "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
      "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
      "pnpm check:changeset-gate-self-tests :: exit 0",
      "pnpm check:corpus-claim-drift :: exit 0",
      "pnpm check:cross-package-test-inputs :: exit 0",
      "pnpm check:dispatcher-error-vocabulary :: exit 0",
      "pnpm check:doc-anchors :: exit 0",
      "pnpm check:doc-authoring :: exit 0",
      "pnpm check:docs-audit-scope :: exit 0",
      "pnpm check:docs-redirects :: exit 0",
      "pnpm check:docs-single-h1 :: exit 0",
      "pnpm check:docs-spec-enumerations :: exit 0",
      "pnpm check:docs-transcript-drift :: exit 0",
      "pnpm check:driver-memory-census :: exit 0",
      "pnpm check:dts-closure :: exit 0",
      "pnpm check:dual-build-cjs-loads :: exit 0",
      "pnpm check:engine-double-contract :: exit 0",
      "pnpm check:error-status-conformance :: exit 0",
      "pnpm check:gitlink-declared :: exit 0",
      "pnpm check:i18n :: exit 0",
      "pnpm check:i18n-stale-fill :: exit 0",
      "pnpm check:issue-citations :: exit 0",
      "pnpm check:lean-entry-closure :: exit 0",
      "pnpm check:logger-receiver-detach :: exit 0",
      "pnpm check:manifest-repository-directory :: exit 0",
      "pnpm check:merge-driver :: exit 0",
      "pnpm check:nul-bytes :: exit 0",
      "pnpm check:objectql-double-limit :: exit 0",
      "pnpm check:objectui-changeset :: exit 0",
      "pnpm check:org-identifier :: exit 0",
      "pnpm check:override-consistency :: exit 0",
      "pnpm check:page-declaration-shape :: exit 0",
      "pnpm check:pm-changeset-deadline-census :: exit 0",
      "pnpm check:published-files :: exit 0",
      "pnpm check:published-readme-links :: exit 0",
      "pnpm check:query-options-erasure :: exit 0",
      "pnpm check:react-page-adapter-contract :: exit 0",
      "pnpm check:refd-timer-probe :: exit 0",
      "pnpm check:role-word :: exit 0",
      "pnpm check:service-providers :: exit 0",
      "pnpm check:skill-identifier-liveness :: exit 0",
      "pnpm check:slot-lookup :: exit 0",
      "pnpm check:sourcemap-no-sources-content :: exit 0",
      "pnpm check:test-source-alias :: exit 0",
      "pnpm check:tier-file-adoption :: exit 0",
      "pnpm check:turbo-task-graph :: exit 0",
      "pnpm check:type-check-coverage :: exit 0",
      "pnpm check:type-check-debt :: exit 0",
      "pnpm check:vendor-export-contract-resolve :: exit 0",
      "pnpm check:vendor-version-stamps :: exit 0",
      "pnpm check:watch-hint-literal :: exit 0",
      "pnpm check:where-matcher :: exit 0",
      "pnpm check:workspace-manifest-cycles :: exit 0"
     ],
     "gates_reconcile": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (108 lines, each `cmd :: exit N`) at 0324d20f2: \"Run reconciliation — 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN.\" / \"✓ dispatch-gates --ran: 108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED (a DERIVED zero — all 108 recorded an exit code and none of them is 3).\"",
     "line_budget": "n/a — the diff touches no skills/** and no line-ratcheted ledger. 23 files, +2233/-88 (2321 changed lines, under 3000).",
     "files_changed": [
      ".changeset/22871-mail-ingest-seam.md",
      "content/docs/permissions/system-context.mdx",
      "content/docs/permissions/tenant-audit-census.mdx",
      "docs/audits/2026-08-tenant-audit-write-call-sites.counts.md",
      "packages/services/service-mailbox/README.md",
      "packages/services/service-mailbox/package.json",
      "packages/services/service-mailbox/src/index.ts",
      "packages/services/service-mailbox/src/ingest/dedupe-key.ts",
      "packages/services/service-mailbox/src/ingest/index.ts",
      "packages/services/service-mailbox/src/ingest/mail-ingest-service.test.ts",
      "packages/services/service-mailbox/src/ingest/mail-ingest-service.ts",
      "packages/services/service-mailbox/src/ingest/match-addresses.ts",
      "packages/services/service-mailbox/src/ingest/match-records.ts",
      "packages/services/service-mailbox/src/ingest/messages.fixture.ts",
      "packages/services/service-mailbox/src/ingest/parse-message.test.ts",
      "packages/services/service-mailbox/src/ingest/parse-message.ts",
      "packages/services/service-mailbox/src/ingest/suppression.test.ts",
      "packages/services/service-mailbox/src/ingest/suppression.ts",
      "packages/services/service-mailbox/src/mailbox-service-kernel.test.ts",
      "packages/services/service-mailbox/src/mailbox-service-plugin.test.ts",
      "packages/services/service-mailbox/src/mailbox-service-plugin.ts",
      "packages/services/service-mailbox/vitest.config.ts",
      "pnpm-lock.yaml"
     ],
     "deviations": [
      "File surface. Five paths outside the claim's list, each forced by a measured gate or tool: (1) content/docs/permissions/system-context.mdx gains row 47b, because check:system-context-census refuses mail-ingest-service.ts's two elevation reads (ingest, authorizeRematch) without an anchor (#22870 added row 47 the same way). (2) content/docs/permissions/tenant-audit-census.mdx and (3) docs/audits/2026-08-tenant-audit-write-call-sites.counts.md are regenerated by `node scripts/tenant-audit-census.mjs --write`, and 6 hand-written prose counts move 225 to 228, because check:tenant-audit-census places the 3 new engine writes. (4) packages/services/service-mailbox/vitest.config.ts gains source aliases for @objectstack/types, objectql and driver-sql, because check:test-source-alias's ledger is shrink-only. (5) pnpm-lock.yaml changes beyond the MIME dependency: the workspace links @objectstack/types (runtime, already installed via @objectstack/core, for the shared keysetWalk) and @objectstack/objectql and @objectstack/driver-sql (dev, for the real-engine tests).",
      "pnpm add also re-resolved packages/apps/account's vitest peer variant from esbuild 0.28.1 to 0.28.2, dropping 3 stale snapshot blocks. That is pnpm's normalization, and the lockfile was not hand-edited.",
      "Attachments are not stored, though the card's scope names attachment extraction. sys_attachment exists (service-storage) and sys_mail_message has enable.files: true, but no server-side writer exists: `git grep \"insert('sys_attachment'\"` gives 0 non-test hits, and uploads go through the storage routes. So a forward-as-attachment keeps the forwarder's text and not the forwarded body. No object was invented. Carrier: stage 2 of #22871, or a follow-up the seat files (dedupe words: inbound mail attachments, sys_attachment writer, mail message attachment storage, forwarded message body).",
      "The PR body footer has a blank line between the rule line and the session-URL footer line. AGENTS.md's form puts the footer directly under the rule. The body was written once and stored identical; it was not PATCHed (a change of the PR body belongs to the seat).",
      "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Commits carry AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude) and the PR body carries AGENTS.md's session-URL footer, by the reminder's own precedence sentence.",
      "The first gate battery (at ae14c39c5) ran while I edited files, so its tree was not fixed. The battery cited above is the second, at the final head 0324d20f2, with a clean tree for its whole run."
     ],
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: stage 2 of #22871 · noted, not filed — packages/spec/liveness/field.json's mailAssociation row stays planned + authorWarn. The reader now exists (src/ingest/match-records.ts MailRecordMatcher.match), but nothing feeds it mail until the IMAP connector lands. Its authorHint 'no mailbox service reads this yet' is now literally stale, though the author-visible effect is still absent. The flip to live, citing that symbol, and dropping authorWarn/authorHint are stage 2's, per the row's own note.",
      "carrier: 承接者:无 · noted, not filed — inference from dist mtimes, not measured directly: during the first battery, pnpm check:type-check-debt (run 10:58-11:13 UTC) coincided with a whole-workspace build in this worktree (packages/adapters/hono/dist and apps dists stamped 11:00-11:07). That is a check:* gate building outside os-verify-lock.",
      "carrier: 承接者:无 · noted, not filed — two suppression narrowings, declared and pinned as controls: an MDN (multipart/report; report-type=disposition-notification) is stored, not suppressed, and a message/global (RFC 6532) forwarded part is not read as an attachment source."
     ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: stage 1, PR #23250 at 0324d20f2 (Part of #22871). Next: the contract-tier review, then the queue

    domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H · 2026-10-13T12:02Z · holder of claim 6142226687. Report: 6145035987. Thread-read: the report.

    Read on GitHub:

    • Shape. The PR is a draft against main. Line 1 is Part of #22871, with no closing keyword beside any card number. Line 2 is Clause-②: yes, naming the new export. It changes 23 files (+2233 / −88 = 2321 lines, under the human-merge threshold). check-governed-merges --pr 23250: not governed. No content/docs/releases/ change, and nothing in packages/connectors/**. The changeset is '@objectstack/service-mailbox': minor.
    • The seam (ruling 6116489638, Q1 A, Q3 C phase 1 and Q4 A; triage 6137138392, stage 1), read in the diff:
      • MailIngestService, registered by MailboxServicePlugin.init() as the kernel service mailbox and exported.
      • ingest refuses anything but the system context with PERMISSION_DENIED.
      • rematch (authorizeRematch) admits the system context, or a caller in the mailbox's organization who holds manage_mailboxes or is its owner_user_id. Another organization's mailbox answers RECORD_NOT_FOUND.
      • The matcher (match-records.ts) reads only email fields flagged mailAssociation: true, under the system context and filtered to the mailbox's organization_id. It skips an object with no organization column, saying so once. It decides exact equality on the value read back, so a substring never matches.
      • There is no resolver contract (Q1 A).
      • match_addresses carries outer / attachment source markers. No code path writes inline (service-mailbox: inline-forward parsing for the Gmail, Outlook and Apple Mail "Forwarded message" blocks — the chained follow-up of #22871 (ruling Q3 C staged) #23215).
      • Re-match only adds links (Q5 A).
    • The one new third-party dependency: postal-mime@^4.0.5 (MIT-0, zero dependencies, ESM and CJS). The PR body measures the alternatives. check:osv-exemptions and check:undeclared-dep-imports exit 0.
    • Evidence accepted:
      • Gates: 108 derived and 108 run, all exit 0, with a --ran reconcile of 0 NOT-MEASURED, on the final head with a clean tree.
      • Suites: 6 files, 64 tests, on a real ObjectQL over driver-sql / SQLite and a booted kernel. A dist/index.cjs probe ingests a message.
      • Ablations: 15, each restored by blob hash. 13 went red as predicted, and 2 stayed green, as stated, because another pin holds the same behavior.
      • ESLint over the 16 changed .ts files: 0 findings.
    • CI at this read: 22 success and 5 skipped (on the roster), with 12 in progress. 0 failures.

    Deviations, one line each:

    • Five paths beyond the claim: the system-context census row 47b, the regenerated tenant-audit census and counts, vitest.config.ts aliases, and the lockfile links. Accepted: each is forced by a named gate.
    • pnpm add re-resolved one unrelated vitest peer variant (esbuild 0.28.1 → 0.28.2): accepted. It is pnpm's normalization, and the lockfile was not hand-edited.
    • Attachments are not stored: accepted. No server-side sys_attachment writer exists, and no object was invented. The card's "attachment extraction" stays on this card for stage 2, and the stage-2 dispatch carries it.
    • A suppressed message writes no row: accepted. association_state declares no suppressed state, as the PR body states.
    • The PR body footer has a blank line under the rule: cosmetic, left as is.

    Report items, one line each:

    • The mailAssociation liveness row stays planned: stage 2's, when the connector feeds the reader. Carried on this card.
    • Two suppression narrowings (an MDN is stored, which is pinned; a message/global forwarded part is not read, which is not pinned): Acceptance notes. Both are declared in the PR body.
    • A check:* gate possibly building outside the verify lock: dropped. It is inferred from dist mtimes, not measured.

    Next: a CONTRACT_REVIEW_TIER review by an isolated subagent on this head, then the pre-queue checks and the queue. The card stays open and pm:dispatched for stage 2 (connector-imap), which follows this landing.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ Contract review PASS; stage 1, PR #23250 at 0324d20f2, is ready with auto-merge armed (Part of #22871)

    domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H · 2026-10-13T12:23Z · holder of claim 6142226687. Thread-read: 6145094803.

    • Contract review on this head: 6145416680, CONTRACT_REVIEW_TIER, PASS. It judges right:
      • the kernel service mailbox and the 18 exports, with the same list in index.ts, the changeset and the PR body;
      • Q1 A field-level matching with no resolver;
      • Q3 C phase 1's outer / attachment sources, with inline declared and never written;
      • Q4 A on re-match and the system-only ingest;
      • organization scoping on every match read and every written row;
      • additive re-match;
      • dedupe and suppression expressed only through declared fields;
      • postal-mime as proportionate;
      • minor with Clause-②: yes.
    • Pre-queue checks on 0324d20f2:
      • 37 success and 5 skipped, every skip on the EXPECTED_SKIPS roster; 0 failures; mergeable: clean.
      • check-governed-merges --pr 23250: not governed, 2321 changed lines.
      • The body carries Part of #22871 and no closing keyword, so this card stays open.
    • Landed through the relay: pr_ready + automerge_enable (run 38396797855).

    Carried to stage 2 (connector-imap), each a line its dispatch must name:

    • The mailAssociation liveness flip, with a deadline: the record requires it.
      • packages/spec/liveness/field.json's row goes live, with evidence packages/services/service-mailbox/src/ingest/match-records.ts#MailRecordMatcher.match.
      • authorWarn and authorHint are dropped, and the authorWarnedProperties('field') pin and the state counts are updated.
      • It lands before any release train ships this seam. From this landing on, the hint "no mailbox service reads this yet" is false.
    • Attachment storage: the card's "attachment extraction", which stage 1 did not do, because no server-side sys_attachment writer exists.
    • The message/global (RFC 6532) forwarded part: read it as an attachment source, or pin its refusal.
    • MAX_FORWARDED_PARTS = 20: named in the README.

    Next: confirm MERGED, then the mis-close scan and the landing note. Then stage 2's claim follows, as triage 6137138392 orders it. The card stays pm:dispatched with its assignee.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: stage 1, PR #23250 → 82581819b (Part of #22871). The card goes back to pm:queue for stage 2, connector-imap

    domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H · 2026-10-13T12:44Z · holder of claim 6142226687, released by this act. Thread-read: 6145459294.

    • Landed: through the merge queue at 2026-10-13T12:44Z as 82581819b, an ancestor of origin/main. 23 files, +2233 / −88, not governed.
    • Mis-close scan: the body says Part of #22871 and carries no closing keyword. No issue closed in the merge window, and this card stays open.
    • The chain: ruling 6116489638 (on spec + service-mailbox: sys_mail_message (the inbound mirror object) and the association seam — phase 1 of #8998 inbound mail, spec seat #22870); triage 6137138392; claim 6142226687; report 6145035987; ACCEPT 6145094803; contract review PASS 6145416680; queued 6145459294.
    • What holds now:
      • @objectstack/service-mailbox registers the kernel service mailbox (MailIngestService).
      • ingest (system context only) turns one raw RFC 822 message into one sys_mail_message row. It reads the participants (outer, plus attachment from a top-level message/rfc822 part), matches the mailbox organization's email fields that declare mailAssociation: true, writes sys_mail_association links or pools the message, dedupes on dedupe_key, and suppresses auto-replies and bounces.
      • rematch (system, or manage_mailboxes / the owner in the mailbox's organization) only adds links.
    • Left on this card: stage 2, connector-imap. It feeds the seam over IMAP, as triage 6137138392 orders, and declares its own Clause-②: yes as a new package. Its dispatch also carries the four items in 6145459294:
      1. the mailAssociation liveness row goes live (evidence match-records.ts#MailRecordMatcher.match; authorWarn and authorHint dropped) before any release ships the seam;
      2. attachment storage, the card's "attachment extraction";
      3. the message/global forwarded part, read or pinned as refused;
      4. MAX_FORWARDED_PARTS in the README.
    • service-mailbox: inline-forward parsing for the Gmail, Outlook and Apple Mail "Forwarded message" blocks — the chained follow-up of #22871 (ruling Q3 C staged) #23215 (inline forwards) stays Blocked-by: #22871, so it waits on stage 2's landing too.

    Release: session_01BRQ9Mp1yH7R1az6MaUM18H · why: a partial landing (Part of #22871) · to: pm:queue, unassigned, for stage 2 above. Resume-from: none (stage 2 starts from main). This act moves the card pm:dispatched → pm:queue and removes the assignee os-justin.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1, rolling slot (#22871, stage 2 of triage 6137138392: connector-imap feeding the stage-1 ingest seam, Part of #22871) · 2026-10-13T13:56Z
    Session: session_012uFegdfR6K6uqvLzt7XsdZ
    Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22871-s2-connector-imap
    Worktree: objectstack-issue-22871-s2
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    Ruling-ref: 6116489638 on #22870 (batch #319 item 2: Q1 A, Q3 C staged, Q4 A; Q2 C3 and Q5 A stand), recorded on this card in 6116501990; triage 6137138392 (the spec lane claims; one card, two staged PRs); stage 1's carried items 6145459294 and its landing 6145843378 (PR #23250 → 82581819b). All read in this act.
    File surface (at origin/main c0569797a or later; stop on a breach and explain it in the report):

    • packages/connectors/connector-imap/**: the new package. It carries protocol and auth only, following the existing connector convention (connector-rest, connector-openapi, connector-mcp, connector-slack). Its account and credential abstraction is shaped for the per-user mailbox (design decision 4), and the shared mailbox is its first implementor.
    • packages/services/service-mailbox/src/**: the sync half the card scopes to the minimal service. It covers the folder cursor and UID state, the incremental fetch, and the feed into stage 1's MailIngestService.ingest. Dedupe, threading and suppression already live there, so they are reused, not rebuilt.
    • The workspace registrations a new published package needs: the manifests, the tsconfig and turbo wiring, the CI path filters, and the changeset fixed group. Each is placed where the existing connectors place theirs.
    • Stage 1's four carried items (6145459294), each a line of this dispatch:
      1. The mailAssociation liveness flip.
        • packages/spec/liveness/field.json's row goes live, with evidence packages/services/service-mailbox/src/ingest/match-records.ts#MailRecordMatcher.match.
        • authorWarn and authorHint are dropped, and the authorWarnedProperties('field') pin and the state counts follow.
        • This item has a deadline: it lands before any release train ships the seam, because the hint "no mailbox service reads this yet" is false since 82581819b.
      2. Attachment storage, the card's "attachment extraction". Measure it first. If it needs a server-side sys_attachment writer that does not exist, STOP and report it as needs_decision, under the four-axis frame. ⛔ Do not build a platform attachment capability inside this card.
      3. The message/global (RFC 6532) forwarded part: read it as an attachment source, or pin its refusal.
      4. MAX_FORWARDED_PARTS = 20: named in service-mailbox's README.
    • Tests: the card's end-to-end acceptance against an in-process IMAP server double, each clause with a negative control.
    • One .changeset/22871-*.md.
    • ⛔ Out of scope, as the card lists: per-user IMAP sync, CalDAV and calendar, write-back, and the enterprise admin plane.
      • ⛔ Inline-forward parsing is service-mailbox: inline-forward parsing for the Gmail, Outlook and Apple Mail "Forwarded message" blocks — the chained follow-up of #22871 (ruling Q3 C staged) #23215 (Blocked-by: #22871), not this stage.
      • ⛔ No change to stage 1's matching semantics (Q1 A), its sources (Q3 C phase 1), its authorisation (Q4 A) or re-match (Q5 A).
        Container & model: M per triage (a new protocol package, plus the sync loop and four carried items), mode:subagent, model: default tier. If the dev measures it at L, the report says so before building past the protocol package. The change adds a published package, so it owes a review at CONTRACT_REVIEW_TIER before the queue, by an isolated at-tier subagent.
        Clause-②: yes (a new published package, @objectstack/connector-imap, and its exports; the mailAssociation liveness row goes live)
        Responsibility: new package, routed to the spec lane by triage 6137138392 under 强制条款② | no IMAP feed exists today: the stage-1 seam ingests only what a caller hands it | reached by every deployment that configures a shared mailbox
        Thread-read: 6145843378
        Serial constraints cleared (the file lists of all 10 open PRs and all open pm:dispatched claims, read in this act):
    • No open PR touches packages/services/service-mailbox/**, packages/connectors/**, packages/spec/liveness/field.json or .changeset/config.json.
    • service-mailbox: inline-forward parsing for the Gmail, Outlook and Apple Mail "Forwarded message" blocks — the chained follow-up of #22871 (ruling Q3 C staged) #23215 (inline forwards) stays Blocked-by: #22871 and waits on this stage.

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22871,
     "status": "needs_decision",
     "branch": "claude/issue-22871-s2-connector-imap",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23303",
     "session": "session_012uFegdfR6K6uqvLzt7XsdZ — subagent run: the parent PM's session id, as the dispatch, the claim and every commit's Claude-Session trailer carry it",
     "premise_still_valid": true,
     "summary": "Stage 2 of #22871 is delivered as draft PR #23303 (`Part of #22871`), at 27aa15166, merged with origin/main at 175e8d414. The new @objectstack/connector-imap is protocol and auth only, with zero runtime dependencies. It opens a read-only IMAP4rev1 session: EXAMINE, UID FETCH with RFC822.SIZE and BODY.PEEK[], and LOGOUT. It authenticates with AUTHENTICATE PLAIN (SASL-IR when offered), LOGIN with a literal when needed, or XOAUTH2. TLS is the default; plaintext is refused to any host but loopback. Its MailAccount + MailCredentialProvider abstraction is per-user shaped: the credential is resolved per account at every open, and staticCredentials is the shared mailbox's implementor. ./testing exports an in-process ImapServerDouble. In @objectstack/service-mailbox, MailboxServicePlugin({ imap: { accountFor, ... } }) schedules the interval job mailbox:imap-sync on the composed job service and cancels it in destroy(). MailboxSyncService fetches the UIDs above a cursor kept on four new readonly sys_mailbox fields (imap_folder, imap_uid_validity, imap_last_uid, imap_synced_at). It discards the cursor on a UIDVALIDITY or folder change, and hands each message to stage 1's MailIngestService.ingest unchanged, so dedupe, threading, matching, the pool and suppression are reused. Carried items 1 (mailAssociation goes live; its own first commit 610e112bd), 3 (message/global read as an attachment source) and 4 (MAX_FORWARDED_PARTS in the README) are done. Item 2, attachment extraction, is STOPPED per the dispatch: there is no server-side sys_attachment writer on main (H5). That is the open question below, and why the PR says Part of. Size: 3690 changed lines (over the 3,000 human-merge threshold), so this PR lands the Tier-H way. By that measure the stage is at L; a subagent run could not send the early status line.",
     "tests": "At 27aa15166, under os-verify-lock (one lock call, VERDICT command-exit 0): `pnpm --filter @objectstack/connector-imap exec vitest run --maxWorkers=2` gives 2 files, 37 tests passed. `pnpm --filter @objectstack/service-mailbox exec vitest run --maxWorkers=2` gives 8 files, 91 tests passed (stage 1 had 64); these run on a real ObjectQL over driver-sql + better-sqlite3 :memory:, plus a booted ObjectKernel with ObjectQLPlugin, a recording job service and MailboxServicePlugin({ imap }). `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/lint-liveness-properties.test.ts` gives 100 passed. `pnpm --filter @objectstack/spec test:repo` gives 58 files, 1007 passed. Typecheck: `pnpm --filter @objectstack/connector-imap --filter @objectstack/service-mailbox run typecheck` exits 0; tsc --listFiles counts 2/2 and 8/8 test files. Build: a full `pnpm turbo run build --concurrency=2 --filter=!@objectstack/docs` on the merged tree ran 75/75 tasks green, and connector-imap emitted index and testing in ESM, CJS and DTS (check-dts-emitted). Acceptance, the end-to-end file src/sync/mailbox-sync-service.test.ts against the IMAP double: clause 1, a forward lands as ONE row, associated, with the attachment source; clause 2, the second run fetches 0, a re-delivered copy answers duplicate, and a UIDVALIDITY rebuild re-reads 2 as duplicates, count unchanged; clause 3, the message pools, then rematch links it once a contact exists; clause 4, an auto-reply and a bounce are suppressed and the cursor moves past them. Ablation, one-off, 11 legs through scripts/ablation-replace.mjs (WRAP mode, restore trap armed). Each anchor hit x1 -> x0 with the blob change shown on disk, the tests ran on mutated SOURCE (vitest aliases every workspace import to src, no dist), and each file was restored to blob == HEAD with `git diff HEAD` empty (scripted check per leg). All red as predicted: A1 sync hands nothing to ingest (14 red); A2 cursor never applied (5); A2b dedupe key per fetch (2); A2c UIDVALIDITY change keeps the cursor (1); A3 rematch adds no link (1); A4 suppression removed (1); A5 BODY[] for BODY.PEEK[] (1); A5b SELECT for EXAMINE (1); A6 plaintext to non-loopback accepted (1); A7 message/global dropped (2); A8 cursor never saved (5). Interop, one-off, outside the tree: the built dist/index.js was probed against imapkit@5.0.1 and hoodiecrow-imap@3.3.1, installed in an isolated scratch dir with --ignore-scripts. 18 checks, ALL PASS on both; hoodiecrow's no-Seen check is NOT MEASURED there (it has no control API). Lint, narrowed: `eslint --no-inline-config --format json` over the 33 changed lintable files at 27aa15166 gives 33 files, 0 errors, 0 warnings, 0 fatal. The population is eslint.config.mjs files '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}' (the rest of the diff is md/mdx/json), the count is read from the JSON, and the invariance holds: no parserOptions.project, so no type-aware linting can move an untouched file's verdict.",
     "mcp_calls": "0",
     "api_writes": "2 — (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204). Relay run 38433998239 executed POST /repos/objectstack-ai/objectstack/pulls and POST /repos/objectstack-ai/objectstack/issues/23303/assignees; the read-back was identical (15036 bytes, assignee os-support-ai, draft). (2) This os-dev-report comment through scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/22871/comments (relay). 0 label writes: the dispatch's write budget names none, and the diff publishes, so skip-changeset is not owed. The git pushes of the branch are not REST writes.",
     "gates": [
      "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
      "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
      "node scripts/check-changeset-fixed.mjs :: exit 0",
      "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
      "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
      "node scripts/check-ci-filter-parity.mjs :: exit 0",
      "node scripts/check-closing-keyword-parity.mjs :: exit 0",
      "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
      "node scripts/check-comment-mask-adoption.mjs :: exit 0",
      "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
      "node scripts/check-comment-mask-corpus.mjs :: exit 0",
      "node scripts/check-declaration-mirrors.mjs :: exit 0",
      "node scripts/check-declaration-mirrors.mjs --self-test :: exit 0",
      "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
      "node scripts/check-doc-frontmatter.mjs :: exit 0",
      "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
      "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
      "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
      "node scripts/check-docs-section-name.mjs :: exit 0",
      "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
      "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
      "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
      "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
      "node scripts/check-issue-citations.mjs :: exit 0",
      "node scripts/check-keyed-text-bounds.mjs :: exit 0",
      "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
      "node scripts/check-osv-exemptions.mjs :: exit 0",
      "node scripts/check-osv-exemptions.mjs --self-test :: exit 0",
      "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
      "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
      "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
      "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
      "node scripts/check-registry-log-declared.mjs :: exit 0",
      "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
      "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
      "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
      "node scripts/check-scripts-symbol-anchors.mjs :: exit 0",
      "node scripts/check-scripts-symbol-anchors.mjs --self-test :: exit 0",
      "node scripts/check-section-landing-index.mjs :: exit 0",
      "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
      "node scripts/check-self-test-wired.mjs :: exit 0",
      "node scripts/check-self-test-wired.mjs --self-test :: exit 0",
      "node scripts/check-self-test-workflow-commands.mjs :: exit 0",
      "node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0",
      "node scripts/check-system-context-census.mjs :: exit 0",
      "node scripts/check-system-context-census.mjs --self-test :: exit 0",
      "node scripts/check-tenant-audit-census.mjs :: exit 0",
      "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
      "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
      "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
      "node scripts/check-whole-set-label-write.mjs :: exit 0",
      "node scripts/check-whole-set-label-write.mjs --self-test :: exit 0",
      "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
      "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
      "node scripts/osv-base-relative.mjs --self-test :: exit 0",
      "node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0",
      "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
      "node scripts/release-pending-publish.mjs --self-test :: exit 0",
      "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
      "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
      "pnpm --filter @objectstack/spec run check:docs :: exit 0",
      "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
      "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
      "pnpm --filter @objectstack/spec run check:generated :: exit 0",
      "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
      "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
      "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
      "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
      "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
      "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
      "pnpm check:agent-test-spelling :: exit 0",
      "pnpm check:bash32-floor :: exit 0",
      "pnpm check:changeset-gate-self-tests :: exit 0",
      "pnpm check:cli-command-ids :: exit 0",
      "pnpm check:corpus-claim-drift :: exit 0",
      "pnpm check:cross-package-test-inputs :: exit 0",
      "pnpm check:dispatcher-error-vocabulary :: exit 0",
      "pnpm check:doc-anchors :: exit 0",
      "pnpm check:doc-authoring :: exit 0",
      "pnpm check:docs-audit-scope :: exit 0",
      "pnpm check:docs-redirects :: exit 0",
      "pnpm check:docs-single-h1 :: exit 0",
      "pnpm check:docs-spec-enumerations :: exit 0",
      "pnpm check:docs-transcript-drift :: exit 0",
      "pnpm check:driver-memory-census :: exit 0",
      "pnpm check:dts-closure :: exit 0",
      "pnpm check:dual-build-cjs-loads :: exit 0",
      "pnpm check:engine-double-contract :: exit 0",
      "pnpm check:entry-guard :: exit 0",
      "pnpm check:error-status-conformance :: exit 0",
      "pnpm check:gitlink-declared :: exit 0",
      "pnpm check:i18n :: exit 0",
      "pnpm check:i18n-stale-fill :: exit 0",
      "pnpm check:issue-citations :: exit 0",
      "pnpm check:lean-entry-closure :: exit 0",
      "pnpm check:lockstep-package-count :: exit 0",
      "pnpm check:logger-receiver-detach :: exit 0",
      "pnpm check:manifest-repository-directory :: exit 0",
      "pnpm check:merge-driver :: exit 0",
      "pnpm check:nul-bytes :: exit 0",
      "pnpm check:objectql-double-limit :: exit 0",
      "pnpm check:objectui-changeset :: exit 0",
      "pnpm check:org-identifier :: exit 0",
      "pnpm check:override-consistency :: exit 0",
      "pnpm check:page-declaration-shape :: exit 0",
      "pnpm check:parse-guard :: exit 0",
      "pnpm check:platform-checklist :: exit 1 (pre-existing: the same 8 findings at the merge base 175e8d414, measured in a throwaway worktree; no input of it is in this diff)",
      "pnpm check:pm-changeset-deadline-census :: exit 0",
      "pnpm check:pm-dispatch-gates :: exit 0",
      "pnpm check:pnpm-filter-targets :: exit 0",
      "pnpm check:publish-smoke-pin :: exit 0",
      "pnpm check:published-files :: exit 0",
      "pnpm check:published-readme-links :: exit 0",
      "pnpm check:query-options-erasure :: exit 0 (re-run on the clean tree at 27aa15166: the battery run overlapped the ablation window, its first exit 0 is not counted)",
      "pnpm check:ratchet-remedy-authority :: exit 0",
      "pnpm check:react-page-adapter-contract :: exit 0",
      "pnpm check:refd-timer-probe :: exit 0",
      "pnpm check:role-word :: exit 0",
      "pnpm check:service-providers :: exit 0",
      "pnpm check:skill-identifier-liveness :: exit 0",
      "pnpm check:slot-lookup :: exit 0",
      "pnpm check:sourcemap-no-sources-content :: exit 0",
      "pnpm check:test-source-alias :: exit 0",
      "pnpm check:tier-file-adoption :: exit 0",
      "pnpm check:turbo-task-graph :: exit 0",
      "pnpm check:type-check-coverage :: exit 0",
      "pnpm check:type-check-debt :: exit 0",
      "pnpm check:vendor-export-contract-resolve :: exit 0",
      "pnpm check:vendor-version-stamps :: exit 0",
      "pnpm check:watch-hint-literal :: exit 0",
      "pnpm check:where-matcher :: exit 0",
      "pnpm check:workspace-manifest-cycles :: exit 0"
     ],
     "gates_reconcile": "`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran` (132 lines, each `cmd :: exit N`) at 27aa15166: \"Run reconciliation — 132 derived, 132 run, 0 NOT-MEASURED, 0 UNRUN.\" / \"✓ dispatch-gates --ran: 132 derived famil(ies) accounted for — 132 run, 0 NOT-MEASURED (a DERIVED zero — all 132 recorded an exit code and none of them is 3).\" The derivation warns the tree is 3 commits behind origin/main 9c7cfbf0a, and one derivation input (scripts/engine-double-contract.pinned.json) changed there; those 3 commits touch none of this diff's files (`git diff --stat HEAD...origin/main` over the diff's paths is empty). Extra, not derived: `pnpm check:i18n-coverage` exits 0, with none new.",
     "line_budget": "n/a — the diff touches no skills/** and no line-ratcheted ledger. 50 files, +3615/-75 = 3690 changed lines vs the merge base 175e8d414: OVER the 3,000 human-merge threshold (class c landing). LICENSE accounts for 202 of them.",
     "files_changed": [
      ".changeset/22871-imap-mailbox-sync.md",
      ".changeset/config.json",
      "README.md",
      "content/docs/permissions/system-context.mdx",
      "content/docs/permissions/tenant-audit-census.mdx",
      "content/docs/protocol/backward-compatibility.mdx",
      "docs/audits/2026-08-tenant-audit-write-call-sites.counts.md",
      "packages/connectors/connector-imap/LICENSE",
      "packages/connectors/connector-imap/README.md",
      "packages/connectors/connector-imap/package.json",
      "packages/connectors/connector-imap/src/account.ts",
      "packages/connectors/connector-imap/src/imap-error.ts",
      "packages/connectors/connector-imap/src/imap-session.test.ts",
      "packages/connectors/connector-imap/src/imap-session.ts",
      "packages/connectors/connector-imap/src/imap-wire.test.ts",
      "packages/connectors/connector-imap/src/imap-wire.ts",
      "packages/connectors/connector-imap/src/index.ts",
      "packages/connectors/connector-imap/src/testing.ts",
      "packages/connectors/connector-imap/tsconfig.json",
      "packages/connectors/connector-imap/tsup.config.ts",
      "packages/connectors/connector-imap/vitest.config.ts",
      "packages/lint/src/lint-liveness-properties.test.ts",
      "packages/services/service-mailbox/README.md",
      "packages/services/service-mailbox/package.json",
      "packages/services/service-mailbox/src/index.ts",
      "packages/services/service-mailbox/src/ingest/mail-ingest-service.ts",
      "packages/services/service-mailbox/src/ingest/match-addresses.ts",
      "packages/services/service-mailbox/src/ingest/messages.fixture.ts",
      "packages/services/service-mailbox/src/ingest/parse-message.test.ts",
      "packages/services/service-mailbox/src/ingest/parse-message.ts",
      "packages/services/service-mailbox/src/mailbox-service-plugin.test.ts",
      "packages/services/service-mailbox/src/mailbox-service-plugin.ts",
      "packages/services/service-mailbox/src/mailbox-sync-kernel.test.ts",
      "packages/services/service-mailbox/src/objects/sys-mailbox.object.ts",
      "packages/services/service-mailbox/src/sync/index.ts",
      "packages/services/service-mailbox/src/sync/mailbox-sync-service.test.ts",
      "packages/services/service-mailbox/src/sync/mailbox-sync-service.ts",
      "packages/services/service-mailbox/src/translations/en.objects.generated.ts",
      "packages/services/service-mailbox/src/translations/es-ES.objects.generated.ts",
      "packages/services/service-mailbox/src/translations/es-ES.source-hashes.generated.ts",
      "packages/services/service-mailbox/src/translations/ja-JP.objects.generated.ts",
      "packages/services/service-mailbox/src/translations/ja-JP.source-hashes.generated.ts",
      "packages/services/service-mailbox/src/translations/zh-CN.objects.generated.ts",
      "packages/services/service-mailbox/src/translations/zh-CN.source-hashes.generated.ts",
      "packages/services/service-mailbox/vitest.config.ts",
      "packages/spec/liveness/field.json",
      "packages/spec/liveness/state-counts/field.md",
      "packages/spec/llms.txt",
      "pnpm-lock.yaml",
      "scripts/publish-smoke-pack.mjs"
     ],
     "deviations": [
      "Evidence anchor: field.json cites `packages/services/service-mailbox/src/ingest/match-records.ts#MailRecordMatcher`, not the dispatch's `#MailRecordMatcher.match`. check:liveness refuses a dotted anchor (\"an anchor must be one identifier\"). The prose names `match` -> `flaggedFieldsOf`.",
      "File surface beyond the claim, each forced by a named gate or tool: content/docs/permissions/system-context.mdx row 47c (check:system-context-census: the sync's two system-only reads); content/docs/permissions/tenant-audit-census.mdx and docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (regenerated by `node scripts/tenant-audit-census.mjs --write`, plus 6 hand-kept prose figures moved 228->229, 73->74, 123->124, for check:tenant-audit-census); packages/spec/llms.txt (check:llms-txt: 70 -> 71 published packages); content/docs/protocol/backward-compatibility.mdx and scripts/publish-smoke-pack.mjs (`check-lockstep-package-count.mjs --fix`, 71 -> 72; publish-smoke-pack's change is a header comment, and no test file reads it); packages/lint/src/lint-liveness-properties.test.ts (the authorWarnedProperties('field') pin the claim names); root README.md (a package-table row where the connectors are listed); the service-mailbox translations (generator plus hand translation of the 4 new fields in zh-CN, ja-JP and es-ES).",
      "pnpm-lock.yaml: `pnpm install` added the two workspace importer entries and also flipped one unrelated vitest peer variant (esbuild 0.28.2 to 0.28.1, the normalization stage 1 met in reverse). The delta was rebuilt from the base lockfile with only the two importer entries (15 lines) and validated with `pnpm install --frozen-lockfile` (exit 0). So it is a mechanical hand edit, declared here.",
      "H1: no third-party IMAP server double was added, even as a devDependency. hoodiecrow-imap 3.x appeared 2026-10-06/07 after 10 dormant years and was deprecated hours later; its rename imapkit is 6 days old with 11 releases in 2 days. Own double, plus the one-off interop probe in `tests`.",
      "Size: measured at L by changed lines (3690 > 3000). The claim asked for that to be said before building past the protocol package, but a subagent's only channel to its caller is the final hand-off, so no early status line was possible. The PR body's split note says commit 1ddc953ae is self-contained; that holds for the package files, but that commit's lockfile still carries the vitest peer flip that 651a2a1cc removed. A split takes the package files plus the 12-line importer entry. The PR body is not PATCHed; the seat may correct it.",
      "The first gate battery (pre-merge, at bf2f23134) found 7 reds that this PR fixes (the census pair, llms.txt, check:i18n provenance, lockstep count, and dual-build-cjs-loads exit 3 for missing dists, re-run green after the full build). It is superseded by the battery on the merged head, cited in `gates`. In that second battery, `check:query-options-erasure` overlapped the ablation window (16:34:25-16:41:58 vs 16:37-16:41:26), so it was re-run on the clean tree (exit 0) and only the re-run counts.",
      "os-verify-lock: one locked call (all suites plus spec test:repo) held the lock 23m33s, about 20 minutes of it spec test:repo, and the tool flagged holder-side starvation. Another seat's spec test:repo held it 28+ minutes in the same window. Noted, not filed.",
      "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Commits carry AGENTS.md's model-free pair, and the PR body carries the session-URL footer, by the reminder's own precedence sentence. The merge commit fffc37464 carries git's default message and no trailer."
     ],
     "open_questions": [
      {
       "question": "Attachment extraction (carried item 2): the card's scope and the design record's minimal service-mailbox both name attachments, but nothing on main writes sys_attachment server-side. sys_attachment has 0 non-test insert sites; sys_file is written only by service-storage's StorageMetadataStore.createFile behind the upload routes; the kernel `storage` service (IStorageService) is a blob store with no file-row or link verb. Storing a mail attachment, and the body of a message forwarded as an attachment, therefore needs a new platform capability. How is this item delivered?",
       "options": [
        "A — Build the platform writer first, keep the item on #22871. A storage-lane card adds one server-side verb to service-storage: bytes through `storage`, the sys_file row and the sys_attachment link to a parent record, under the system context, with the parent's organization, quota and parent-access hooks. A small #22871 follow-up then stores mail attachments, and each forwarded message/rfc822 or message/global part as an .eml attachment, through it. Cost: storage card M (a new contract surface plus its refusal pins); mail follow-up S. #22871 stays open until both land. Business need: funded by the maintainer-approved design record (\"minimal service-mailbox (... parse, dedupe, attachments)\"), and today a forward-as-attachment loses exactly the forwarded body. Measured pull: 0 in-tree readers of mail attachments (no mail UI yet), while sys_mail_message already declares enable.files: true. Long-term: one writer for every server-side producer (mail, imports, AI output) keeps quota, organization and access in one path; contract-first. AI error: one declared door instead of each service inserting sys_file/sys_attachment rows by hand past the hooks, so it is structurally harder to get wrong. Startup focus: a new platform capability whose only puller is this funded item.",
        "B — Split it off: move attachment extraction to a chained follow-up card, Blocked-by a storage-writer card (A's substance, filed now), and let #22871 close when this stage lands (the seat switches the PR body to `Fixes #22871` before the queue). Cost: S (card edits; the same build as A, later). Business need: delays a funded item; the README already states attachments are absent. Long-term: no workaround code, the decision is deferred rather than bent. AI error: neutral, nothing is declared that the runtime does not do. Startup focus: strongest, no platform capability lands until it is pulled.",
        "C — Store attachment METADATA only (file name, media type, size, content-id) in a new JSON field on sys_mail_message, without the bytes. Cost: S, but it widens a published object with a second representation that sys_attachment rows later make redundant: workaround-shaped on the long-term axis, and a half-capability on the AI-error axis (presence declared, content absent). Not recommended. (Inline bytes in a column was considered and rejected outright: it bypasses storage, quota and access.)"
       ],
       "recommendation": "B, with A's storage card filed in the same act. Startup focus and the measured zero pull favour not building a platform writer inside a mail card, and the dispatch forbids it. The long-term and AI-error axes both favour A's single writer over C's metadata column, and B keeps that path open without a workaround. The business axis (a funded scope item) is the one cost; B carries it on a named chained card rather than on an open #22871. If the maintainer weighs the funded scope above startup focus, A is the same build sequenced on #22871."
      }
     ],
     "out_of_scope_findings": [
      "carrier: 承接者:无 · noted, not filed — `pnpm check:platform-checklist` exits 1 on main: `node scripts/check-platform-checklist.mjs` in a throwaway worktree at the merge base 175e8d414 gives exit 1 with 8 findings (ABSENT SYMBOL anchors into packages/metadata-protocol/src/protocol.ts and service-storage attachment-access-hooks.ts, plus SYMBOL ANCHORS LOST 27, below the floor of 28 in docs/qa/platform-checklist/areas/attachments-storage.json). This diff touches none of its inputs. It is a red repo gate, not a public door, so it is not a filing class here; the seat's main-red check decides. Dedupe words: platform-checklist absent symbol, symbol anchors lost, anonymousFormIntakeOrgScopeRefusal, envWideRawViewRows.",
      "carrier: the release-notes compiler at the next release (maintainer) · noted, not filed — the stage-1 changeset .changeset/22871-mail-ingest-seam.md (not yet released) says 'no mail source feeds the seam'. This PR's changeset says it supersedes that line, and the stage-1 file is not edited.",
      "carrier: 承接者:无 · noted, not filed — sys_mailbox rows still have no administration door. The object is engine-owned with apiMethods get/list since #22870, so a deployment creates a mailbox row only from server code under the system context. The README now says so. It is a product gap for the per-user / admin-plane follow-ups, not a defect of this stage."
     ]
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat answer to the stage-2 report: attachments ruled B (the maintainer may veto in the round report); the mailAssociation flip lands on its own small PR; PR #23303 takes the Tier H route (3690 lines) · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T17:11Z

    The report, read. PR #23303 at 27aa15166 (Part of #22871, draft) delivers the following:

    • @objectstack/connector-imap: protocol and auth only, zero runtime dependencies, a read-only IMAP4rev1 session, TLS by default, and a per-user-shaped account and credential abstraction.
    • The mailbox:imap-sync job in service-mailbox. Its cursor sits on four new readonly sys_mailbox fields, and it feeds stage 1's ingest unchanged.
    • Carried items 1 (the liveness flip, as its own commit 610e112bd), 3 (message/global) and 4 (MAX_FORWARDED_PARTS).
    • The four acceptance clauses against the IMAP double, with 11 ablation legs, each red.

    Item 2, attachment extraction, stopped as the dispatch ordered: no server-side writer for sys_attachment exists on main.

    The open question, ruled B: split it off.

    The mailAssociation flip lands on its own PR.

    PR #23303 takes the Tier H route (more than 3,000 lines, generated files included; no Tier S exception).

    1. Its body's line 1 becomes Fixes #22871, because under ruling B the remainder lives on service-mailbox: store an ingested message's attachments, and each forwarded message part as an .eml attachment, through the server-side writer: the attachment item of #22871, chained #23307. The body names service-mailbox: store an ingested message's attachments, and each forwarded message part as an .eml attachment, through the server-side writer: the attachment item of #22871, chained #23307 for attachment extraction.
    2. The dev adds a ## 维护者速读(草稿) section to the body, in Chinese and from the business side, with the seat's opinion left blank.
    3. The review. The seat ACCEPT and the at-tier contract review run on the head as usual (Clause-②: yes: a new package, four new sys_mailbox fields, and the liveness row).
    4. The handoff. On PASS, 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 stays visibly pending for the maintainer:
      • needs-user-decision on the PR;
      • the final 维护者速读 posted as a comment;
      • a review requested from os-zhuang and hotlong;
      • a line in the round report under "awaiting a human merge".
        The seat does not arm auto-merge.

    Out-of-scope notes, each with no other carrier:

    • check:platform-checklist is red on main at 175e8d414, with 8 findings and no input in this diff.
    • The stage-1 changeset's "no mail source feeds the seam" sentence is superseded by this PR's changeset.
    • sys_mailbox has no administration door.
      This seat checks the first against main's CI before the queue. The other two stand as noted.

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim amendment (#22871 stage 2, claim 6147217218): one more branch, under seat answer 6151023438 · 2026-10-13T17:12Z
    Session: session_012uFegdfR6K6uqvLzt7XsdZ
    Account: os-support-ai
    Branch: claude/issue-22871-s2-liveness-flip (added). claude/issue-22871-s2-connector-imap (PR #23303) stays.
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface of the added branch: exactly commit 610e112bd's change off current main, which is packages/spec/liveness/field.json, packages/spec/liveness/state-counts/field.md and packages/lint/src/lint-liveness-properties.test.ts, plus one .changeset/22871-*.md if the gates owe one. ⛔ Nothing else.
    Why: the mailAssociation hint carries a release deadline, and PR #23303 (3690 lines) waits on a human merge. The new PR is Part of #22871 and is landed through the queue after an at-tier review.
    Thread-read: 6151023438


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat note: contract review PASS 6151327338 on PR #23303 (27aa15166) adopted; one hardening patch round before the Tier H handoff · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T17:29Z

    What the record confirms. It judges right:

    • the scope: protocol and auth only, and stage 1 fed unchanged;
    • the security guarantees: TLS verified by default; plaintext refused before a socket opens, except to loopback; a read-only session (EXAMINE and BODY.PEEK[], with no STORE, EXPUNGE or SELECT path); escaped command arguments;
    • the sync state: the UIDVALIDITY reset, advance only after handover, a crash re-fetching duplicates and never skipping, and teardown;
    • the four readonly sys_mailbox fields;
    • the liveness flip;
    • minor / minor / patch with Clause-②: yes;
    • Fixes #22871 under ruling B.

    Why the head moves anyway. The record names three hardening gaps against a hostile IMAP server, which is an untrusted peer:

    1. Neither the number of parts per response nor the untagged responses per command is bounded, so memory grows without limit under a trickle.
    2. The sync's maxMessageBytes is not passed to the session as maxLiteralBytes. A server that omits or understates RFC822.SIZE above the 64 MiB session default then fails the whole run on every run: a stall.
    3. A batch is bounded by count, not bytes: 25 × 32 MiB in the worst case.

    The PR waits on a human merge anyway, so these are fixed in this PR rather than carried. They are all inside the claim's surface (connector-imap, service-mailbox sync).

    The patch round (dev):

    • a cap per response and per command (bytes or count), with a refusal pin;
    • maxLiteralBytes passed from the sync's bound, with a small envelope, plus a pin that a lying server's oversized message is skipped, not stalled;
    • a byte budget per batch;
    • in the double's header and the connector README, the \Seen sentence corrected to what the code does: only BODY[] under SELECT marks it;
    • once the liveness flip PR carries its own changeset, this PR's changeset drops the @objectstack/spec: patch line and the spec bullet, so the release notes do not carry the flip twice.

    Then a delta review runs on the new head, followed by the Tier H handoff (seat answer 6151023438).

    Noted, not changed:

    • localhost is accepted by name. A spoof needs the host itself compromised.
    • imap_synced_at is a status column that no code reads.
    • Listing cost on a large folder is a note for the per-user phase.

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT (seat review, delta): PR #23303 at 44aabc877 after the hardening round; PR #23314 is in a one-line trim · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T20:20Z

    The hand-back, read (all three tasks of the seat answer 6151023438 and the seat note 6151344448):

    Accepted with notes:

    Next, in order:

    1. needs:contract-review goes on 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 in this act. The delta review runs on 44aabc877 against the PASS 6151327338.
    2. On PASS, the Tier H four-piece: needs-user-decision on the PR, the final 维护者速读 as a comment, a review requested from os-zhuang and hotlong, and a line in the round report. No auto-merge.
    3. PR fix(spec): the mailAssociation liveness row goes live — the mailbox matcher reads it, so the stale author warning is dropped #23314: the trimmed head, then an at-tier review, then the queue. It lands first, and feat(connector-imap, service-mailbox): the IMAP mail source — a read-only IMAP connector and the mailbox sync that feeds the ingest seam #23303 then merges main, which makes 610e112bd a no-op.

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat note: the delta review on PR #23303 at 44aabc877 is FAIL (6154709411); one fix round is ordered after the #23314 trim · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T20:37Z


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (part): PR #23314 → 777eed2bc (Part of #22871). The mailAssociation liveness row is live, and the stale author warning is gone

    domain:spec seat 3 (#18883) · os-support-ai · session session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T22:03Z · holder of claim 6147217218 (amendment 6151040021).

    • Path:
      • The seat ACCEPT 6154419315; the one-line note trim 8ccbeda9d.
      • The contract-tier review PASSed on 8ccbeda9d (6155577921).
      • Every check was green or an expected skip (11 of 11 in the roster), with mergeable: clean. check-governed-merges reported it NOT governed, at 31 lines.
      • Ready and auto-merge went on at 2026-10-13T21:32Z. The queue merged it at 2026-10-13T22:02Z.
    • On main at 777eed2bc: 4 files, +19 / −12. The patch-id --stable equals that of 280806912..8ccbeda9d: byte-identical to the reviewed head.
    • What changed for an author: os lint no longer warns liveness-planned-property on mailAssociation, because the mailbox ingest seam's matcher reads it (evidence match-records.ts#MailRecordMatcher). The release carries it as '@objectstack/spec': patch.
    • The card stays open (Part of). 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, the IMAP connector and sync (Tier H), closes it. Its fix round from the delta FAIL 6154709411 is in dev, and it now merges main. The one-line conflict on the row's note resolves to main's side, which makes 610e112bd a no-op there. pm:dispatched stays.

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat note: PR #23303 PASSes its delta review at 671693ad6 (6157310691); the Tier H handoff is done, and it waits for a human merge · domain:spec seat 3 (seat post #18883) · session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-13T23:27Z

    • Review history:
      • PASS 6151327338 on 27aa15166.
      • FAIL 6154709411 on 44aabc877: the cursor did not pass a trailing skip.
      • PASS 6157310691 on 671693ad6, with no failing finding.
        CI is green (38 success, 5 roster skips), mergeable is clean, and the size is 4168 lines, over the threshold.
    • The Tier H four-piece, done in this act:
      1. needs-user-decision is on the PR, and needs:contract-review is off.
      2. The final 维护者速读 is posted on the PR (6157331354).
      3. A review is requested from os-zhuang and hotlong.
      4. It will appear in the round report under "awaiting a human merge".
    • No auto-merge. The PR stays draft. After an authorized approval, this seat readies it and queues it, then posts the landing record here and takes pm:dispatched off. This card closes on that merge (Fixes #22871).
    • Carried, not fixed: a run's time is unbounded under a trickling server (memory stays bounded). Carrier: the per-user mailbox phase.

    Generated by Claude Code

  15. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23303 → 3d830b77c (Fixes #22871). The IMAP mail source: a read-only @objectstack/connector-imap and the mailbox sync that feeds the stage-1 ingest seam

    domain:spec seat 3 (#18883) · os-support-ai · session session_012uFegdfR6K6uqvLzt7XsdZ · 2026-10-14T00:49Z · holder of claim 6147217218 (amendment 6151040021).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions