docs(platform-objects): widen sys_email.error description to cover pre-delivery rejections - #15276
Conversation
…ndeclared request keys are compile errors (#15215) * wip(runtime): type the packages-domain protocol service handle Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * wip(runtime): add the packages-domain protocol handle typing pin Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * chore(changeset): patch note for the packages-domain protocol handle typing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * docs(permissions): re-anchor the system-context census rows moved by the typing block Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * docs(permissions): regenerate the system-context census from the merged tree Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N --------- Co-authored-by: Claude <noreply@anthropic.com>
…15220) * fix(objectql): publish the record's organization on every DataEvent (#14970) `DataEventSchema.organizationId` was declared and published by the spec half but populated by nothing, so every `data.record.*` event went out with the key absent — which the contract requires a consumer to read as "this record is behind no organization wall". `publishDataEvent` now resolves it from the row itself: the written record on `created`, the post-state on `updated`, and the by-id branch's already-read pre-image on `deleted`, so no per-event read is bought. The record's organization, never `ExecutionContext.tenantId` — that is the caller's active org, and the two diverge on exactly the system/unscoped write this key most needs to label correctly. Absence keeps one spelling: the key is omitted, never `''` (which the schema refuses outright, dropping the whole event) and never an explicit `undefined` (which survives `parse` as a present key). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * docs(permissions): re-anchor the system-context census after the engine line shift Mechanical repair by `node scripts/check-system-context-census.mjs --fix`, the only correct writer for this table. Pure line rot: the `eventOrganizationId` helper and its threading shifted every later line in `packages/objectql/src/engine.ts`, so 14 anchors (15 citation sites — one source line is cited twice) pointed at the wrong lines. No population and no classification change: still 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read — the same figures as before the shift. `--fix` did not refuse, and the diff is digits and nothing else (12 lines added, 12 removed, identical once digits are stripped). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ --------- Co-authored-by: Claude <noreply@anthropic.com>
…aude/ branches (#15224) * ci(reaper): arm the merged-branch reaper for scheduled deletion of claude/ branches Flips .github/workflows/merged-branch-reaper.yml from report-only to the scheduled weekly deletion the maintainer ruled on 2026-09-04 (issue #12771, decision batch #30), reaffirming the 2026-08-31 ruling under the base-ref guard PR #15144 landed. Deletion is a SEPARATE job (`reap`), because `permissions:` is scoped per job. `sweep` keeps `contents: read` + `pull-requests: read` and remains structurally incapable of deleting a ref; `reap` holds the only `contents: write` in the file, consumes the `reapable` list `sweep` publishes as a job output, and computes no classification of its own. Fences: - `reap` never runs on `pull_request` — the self-exercising run stays a dry run — and its `if:` is an allowlist of `schedule` plus a `workflow_dispatch` on which the operator explicitly set `dry_run: false`. - the new `dry_run` workflow_dispatch input defaults to true, so the manual path is fail-closed. - the base-ref guard, `PREFIX`, `BASE_REF`, the grace window, the schedule and the `is-ancestor` prohibition are all untouched. - the whole deletion list is printed to the run log before the first delete. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * test(reaper): pin the deletion hand-off and fence the delete job structurally The contract harness drives the `sweep` classifier and can say nothing about the job that deletes — deletion deliberately lives outside the extracted script, so what the harness judges stays a classification rather than an action. Two additions close that gap. 1. The hand-off. `sweep` now publishes `reapable_branches`, the machine-readable half of the list it prints, and `reap` consumes that and nothing else. Scenarios G1/G2/R1 pin that the list EQUALS the reapable bucket — same members, same order — over a population carrying one branch in every bucket, and mutations M13/M14 drive both directions red (held branches leaking in; the list not published at all). 2. The fence. `reapFenceFailures()` parses the shipped YAML and asserts the delete job's structure: its `if:` excludes `pull_request` and gates `workflow_dispatch` on `inputs.dry_run == false`; it declares `contents: write` and is the ONLY job in the file that does; the top-level grant stays `contents: read`; it still `needs: sweep`. New self-test battery 6 drives six mutations of the workflow text to red, each asserting its anchor was present first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * ci(reaper): put every excluded bucket on the run-log audit line The maintainer's ruling names the run log as the audit trail, and the notice line named three of the seven buckets — reapable, mergedElsewhere, noPr. The other four (open, closedUnmerged, grace, protectedBranch) lived only in the step summary and the uploaded artifact, so the log alone could not answer "what did it hold back, and why". Also retires two strings that stopped being true when the reaper was armed: the summary heading said "DRY RUN. Nothing was deleted." of a run that may now delete in a later job, and the notice said "Nothing was deleted" of the whole run rather than of this job. Both now speak for the `sweep` job only, which is the thing they were ever really asserting — its token grant is `contents: read` and that has not changed. No classification changed: the buckets, the guard, the grace window and the step outputs are byte-identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --------- Co-authored-by: Claude <noreply@anthropic.com>
…the TS2341 x3 it hid (#15152) * wip: onboard service-automation typecheck, fix TS2341 residue * wip: onboarding gate registry entry + changeset * fix(scripts): re-measure this entry's provenance totals on the merged tree The `service-knowledge` onboarding landed on `main` between this entry's first reading and this merge, so every absolute in its provenance block (programs, pairs, packages, clean count) was a number about a tree that no longer exists. Re-taken with `--list` on the merge commit itself, all four rows plus the before/after pair, by varying only what the `typecheck` script names: no `typecheck` script absent 120 programs / 293 pairs names tsconfig.json absent 120 programs / 293 pairs names tsconfig.test PRESENT 121 programs / 302 pairs names both (the card) PRESENT 121 programs / 302 pairs before 59 of 78 packages, 120 programs, 293 pairs, 19 clean after 60 of 78 packages, 121 programs, 302 pairs, 18 clean The deltas this block actually claims (+1 package, +1 program, +9 pairs, one per dep) are unchanged; only the absolutes moved, and the block now says which merge moved them. The sibling entries' own blocks keep their own historical readings untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y --------- Co-authored-by: Claude <noreply@anthropic.com>
…e-delivery rejections `sys_email.error` was declared as "Transport error message when status=failed", but since #14371 EmailService.recordRejectedMessage also writes status=failed rows for messages rejected by normalizeMessage before they reach a transport (prefixed "rejected before delivery: ..."). The declared field help was narrower than what the column actually holds. Widen the description (wording settled in triage, issue comment 5504375428) and regenerate the platform-objects i18n bundle with its own tooling (node scripts/check-i18n-bundles.mjs --write) rather than hand-editing the generated file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
…s-email-error-description
…n widening Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c1e84067765fbc5ab4e1fbadd460b173dbd08b20 && git checkout c1e84067765fbc5ab4e1fbadd460b173dbd08b20
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 460134af85f7ab2cf68abc62f1bbb9783b8899dd 71590c4709a91ceada04ce9f8f929b7e32e002be && git checkout -B drift-repro 460134af85f7ab2cf68abc62f1bbb9783b8899dd && git merge --no-ff 71590c4709a91ceada04ce9f8f929b7e32e002be
node scripts/docs-audit/affected-docs.mjs --json 460134af85f7ab2cf68abc62f1bbb9783b8899dd
|
…s-email-error-description
Fixes #14372
What
sys_email.error(packages/platform-objects/src/audit/sys-email.object.ts) was declared as:Since #14371,
EmailService.recordRejectedMessage(plugin-email/src/email-service.ts:677/:852) also writesstatus: 'failed'rows for messagesnormalizeMessagerejects before they reach a transport (unsendablefrom, no recipient, no subject, no body), prefixedrejected before delivery:(:875). The declared field description was narrower than what the column now holds.Widened to the wording settled in triage (issue comment 5504375428, reconfirmed in 5536386432/5537220287):
How
Edited only the declaration (
sys-email.object.ts:195) and regenerated theplatform-objectsi18n bundle with its own tooling —node scripts/check-i18n-bundles.mjs --write— rather than hand-editingen.objects.generated.ts(itshelp:key at line 2479, per the source-of-truth guardrail in this repo's docs). Confirmed the generatedhelp:string actually changed and thatcheck:i18nre-verifies clean afterward.The translated locales (
zh-CN/ja-JP/es-ES) keep their existing (now-stale relative to the new English) human translations by the extractor's own merge-mode design — that is expected behaviour for this bundle (untouched keys are not drift) and outside this card's scope.No behaviour change: no schema key added/removed,
recordRejectedMessageuntouched, no column change.Net change: exactly 3 files, +25/-2 (matches GitHub's own
changed_files/additions/deletionson this PR as of HEAD71590c470) —.changeset/sys-email-error-description-widen.md,packages/platform-objects/src/apps/translations/en.objects.generated.ts,packages/platform-objects/src/audit/sys-email.object.ts. The commit list carries extra entries from a merge-base hiccup fixed mid-review (see note at the bottom) — those are not part of this PR's actual diff.Verification
git greppositive controls onorigin/main@97bcd99e:recordRejectedMessage(email-service.ts:677,:852) and"rejected before delivery: "(:875) both fire — premise re-confirmed live (was 0 on 2026-09-02, non-zero now).pnpm --filter @objectstack/platform-objects test— 33/33 files, 518/518 tests pass.pnpm --filter @objectstack/platform-objects typecheck— clean.pnpm check:i18n(full, 9 packages) — green; filtered re-run —platform-objects in sync (11 bundles).pnpm check:i18n-stale-fill— green (this is the gate that would have caught a stranded stale leaf on a revised source string; 0 stale-fill leaves).git grepclean).Local gate families (per family, re-derived with
node scripts/pm/dispatch-gates.mjs, no path args, on the corrected 3-file surface — merge base460134af8— at HEAD71590c470)37/37 green:
node scripts/check-adr-0087-registration.mjs --self-testnode scripts/check-changeset-no-major.mjs --self-testnode scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-adoption.mjsnode scripts/check-comment-mask-adoption.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/check-empty-changeset.mjs --self-testnode scripts/check-keyed-text-bounds.mjsnode scripts/check-keyed-text-bounds.mjs --self-testnode scripts/check-plugin-teardown-shape.mjsnode scripts/check-plugin-teardown-shape.mjs --self-testnode scripts/check-system-context-census.mjsnode scripts/check-system-context-census.mjs --self-testnode scripts/check-undeclared-dep-imports.mjsnode scripts/check-undeclared-dep-imports.mjs --self-testnode scripts/docs-audit/check-affected-docs.mjsnode scripts/docs-audit/check-drift-comment.mjsnode scripts/pm/release-rehearsal-clone.mjs --self-testpnpm check:changeset-gate-self-testspnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:dual-build-cjs-loadspnpm check:i18npnpm check:i18n-stale-fillpnpm check:logger-receiver-detachpnpm check:nul-bytespnpm check:objectui-changesetpnpm check:page-declaration-shapepnpm check:pm-half-statespnpm check:published-filespnpm check:refd-timer-probepnpm check:slot-lookuppnpm check:test-source-aliaspnpm check:type-source-resolutionpnpm check:watch-hint-literal(29 matched by path + 2 by change kind [
check:i18n,check:i18n-stale-fill, since this diff revises an existing source string] + 6 declared whole-tree.)pnpm check:dual-build-cjs-loadsneeds a fullpnpm buildfirst (it refuses with exit 3 and states nothing was measured otherwise) — done before this run.Changeset
@objectstack/platform-objectsships this description as field metadata (Studio reads it as the field's help text), so this is not comment-only — added apatchchangeset.Note: merge-base correction mid-review
An earlier push in this PR's history briefly desynced its merge-base from
origin/main(agit filter-branchused to clean up commit-message trailers rewrote commits that had been merged in fromorigin/main, replacing them with content-identical-but-different-SHA copies — sogit merge-base origin/main HEADstopped advancing past this branch's original fork point, and GitHub's three-dot diff/file-count briefly over-reported). Fixed by merging the currentorigin/mainin again (no rebase, no force-push) —git merge-base origin/main HEADnow correctly resolves toorigin/main's real tip, and the three-dot and two-dot diffs agree on exactly the 3 files above. The gate results in this body were re-derived and re-run fresh after that fix, superseding an earlier report-comment reading on the issue that (harmlessly, since content never changed) predates it.Generated by Claude Code