Skip to content

Commit bfe3abc

Browse files
committed
Merge branch 'main' into claude/issue-18224-wire-issue-citations-to-ci
PR #19225 made the half-state patrol callable: the patrol's steps now live in the composite action `.github/actions/half-state-patrol`, and the workflow `uses:` it. This branch adds the report-only `--census` leg of the citation gate to the OLD shape, so the two sides overlapped in two places. Conflict 1 (`on.pull_request.paths` + the `permissions:` note) is additive in both directions: the branch's `scripts/check-issue-citations.mjs` trigger row and main's `.github/actions/half-state-patrol/**` glob name different files, and main's rewritten `issues: write` paragraph is kept with this branch's `pull-requests: read` paragraph appended under it. Conflict 2 is not textual. Main emptied that region -- every step in it moved into the composite action -- and the census step is the one thing there that main did not relocate. It stays a step of the CALLER, and the placement is the argument rather than an accident: - `scripts/check-issue-citations.mjs` is objectstack-only, so inside the action its "Locate the patrol sources" step would either have to name it and refuse to run in every sibling that adopted the action, or not name it and fail on a missing file there. The repo-name gate only works in the caller. - The action runs its scripts from `steps.sources.outputs.root` (the tree the action ships from) while the board's checkout is `github.workspace`. A census of the WORKSPACE tree run from inside the action would, in a sibling, census this repo's release pages and report the count under the sibling's name. - `pull-requests: read` is granted by this workflow's `permissions:` block, which a composite action cannot carry and the action's inputs do not name. One behaviour had to be spelled rather than inherited: the step's guard is now `${{ !cancelled() && github.repository == '...' }}`. In the old shape the census sat above the only step that failed the job, so it ran whatever the sweep returned; the patrol is now one `uses:` step that goes red itself, and a default `success()` would have skipped the census on exactly the runs where the patrol is down. This is the guard main gave the closed-card sweep one step up, for the reason its own comment states. Nothing else changed: the blocking diff-scoped step in `lint.yml` and the `OS_GATE_MERGE_GROUP_BASE_SHA` declaration are untouched, and the census command stays a bare literal path so the gate derivation keeps seeing it. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
2 parents 7d60a60 + 43d492f commit bfe3abc

17 files changed

Lines changed: 1227 additions & 367 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
`installPackage`'s docblock no longer names `marketplace` as the capability that governs package persistence — after #17676 ruling A′ item 1 that half is `package-registry`, and `marketplace` names only the optional catalogue (#17676).
6+
7+
Clause-②: no
8+
9+
The shipped note read *"when the `package` service is absent (e.g. the `marketplace` capability is off)"*. That parenthetical was accurate when it was written and stopped being accurate when the carve-out landed: `packages/spec/src/kernel/platform-capabilities.ts` now carries `marketplace` and `package-registry` as two tokens, with the `sys_packages` container and its boot hydration under the second — a core capability mounted always — and browsing left under the first. A consumer reading this docblock in an editor, out of `dist/index.d.ts`, was being pointed at the wrong switch.
10+
11+
- **⛔ No behaviour moves, and this is not the card's defect being repaired.** The in-memory-only branches in `installPackage` and `updatePackage` are byte-identical. Ruling A′ item 2 keeps them deliberately, as the documented degraded path for reduced hosts — a host that mounts no provider must still be able to install a package for the life of its process — so the note now says that too, rather than leaving the branch reading like an oversight. #17676 stays open.
12+
- **The note also records what is NOT true yet, measured rather than assumed.** `Serve.CAPABILITY_PROVIDERS` (`packages/cli/src/commands/serve.ts`) keys `marketplace` and does not key `package-registry`, so the always-on token is force-appended to every app's `requires` and then resolves to no provider — silently, because the resolver warns only for tokens outside the vocabulary. A stock boot still takes the absent-service branch. That half of the ruling belongs to the capability resolver and is not in this package.
13+
- `updatePackage`'s docblock points at the same note, since the ruling names both primitives.
14+
15+
Published surface: doc comments only. `dist/index.js`, `dist/index.cjs`, `dist/index.d.ts` and `dist/index.d.cts` all carry the corrected text — tsup keeps JSDoc, which is why this ships at all — and no export, type, signature or runtime string moves.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
docs(spec): scope the email-template locale-floor claims to a call that NAMES a locale (#18056)
6+
7+
Clause-②: yes — no accept set moves (no key is added, removed or revalidated),
8+
but what a PUBLISHED package states about its own resolution contract is
9+
corrected, which is a contract act in substance.
10+
11+
`packages/spec` stated two different rung counts for one resolution.
12+
`EmailTemplateDefinitionSchema.locale`'s `describe` and the
13+
`EMAIL_TEMPLATE_FLOOR_LOCALE` docblock published **one** retry rung and an
14+
explicit "no fallback floor at all"; `SendTemplateInput.locale` in
15+
`contracts/email-service.ts`, same package, documents a **three-rung** ladder
16+
whose third rung is reachable exactly on the path the first says cannot exist.
17+
18+
Measured against the runtime rather than reconciled by preference —
19+
`EmailService.resolveAndRenderTemplate` and `createSysEmailTemplateLoader` in
20+
`@objectstack/plugin-email`, and the CI pins in
21+
`template-locale-resolution.test.ts` — the three-rung text is the correct one:
22+
23+
1. the named locale, matched exactly (no language-subtag folding);
24+
2. the literal `en-US`, which is also where a call naming no locale starts;
25+
3. **only for a call that named no locale**, and only when the bundle carries
26+
no `en-US` row: the bundle's lowest locale tag.
27+
28+
So a bundle with no `en-US` row dead-letters (`TEMPLATE_NOT_FOUND`, permanent)
29+
for every recipient whose locale was NAMED, and silently renders whichever
30+
language sorts first for every call that named none. The shipped declaration
31+
promised the loud permanent refusal on the path where the runtime performs the
32+
silent fill; an author reading it was told a missing locale always
33+
dead-letters. Both call shapes are now named wherever the floor is claimed, and
34+
the ladder itself is stated in one place only.
35+
36+
Also corrected: `SendTemplateInput.template` said the service "picks the
37+
best-matching locale row", which the resolver has never done — there is no
38+
best match and no folding, only the ladder above.
39+
40+
`defineStack`'s `warnEmailTemplateLocaleFloor` gains a declaration of the two
41+
shapes it deliberately does NOT examine (a stack whose `i18n.supportedLocales`
42+
is absent or empty; a bundle whose tags all fall outside `supportedLocales`) —
43+
both can still ship a floorless bundle. Its control flow is unchanged — the same
44+
bundles warn, once each, and the warning stays advisory — but the emitted warning
45+
TEXT did change, and now names BOTH call shapes: it says the bundle has no
46+
fallback floor *for a send that names a locale*, and adds that a send naming NO
47+
locale does not fail but drops to that bundle's lowest tag and renders it
48+
silently. A test asserting on the old wording needs updating. Whether either
49+
undeclared shape should warn is the ADR-0049 enforce-or-remove question and is
50+
not answered here. Both shapes are now pinned against a warning discriminator so
51+
neither can change without a test saying so.

‎.changeset/18677-validate-per-package-authoring-pass.md‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ After: both report 4, the same set, in the same order.
1717

1818
**The loop is now one seam, not two copies.** `runPerPackageAuthoringRules` lives beside `artifactPackages` / `packageBodyAsStack` in `utils/artifact-packages.ts`, whose header already forbids a second copy of that shape by name. What would have drifted between two hand-written loops is not the package reading but the **verdict** — the de-duplication key, the severity split, the `where` prefix. `os build`'s observable output is unchanged (text face byte-identical modulo timings; `--json` payload identical).
1919

20-
**Severity mapping is `os build`'s, unchanged.** A per-package `error` refuses (exit 1); an advisory joins `warnings`. So `os validate` is narrowed only to the bar the command that *ships* already holds: every input it can now refuse is one `os build` already refuses, which means **nothing that builds today stops validating**. No newly-refused input could be exhibited on any fixture — across the repo's own two-package example and three constructed variants the observable change is advisory-only, because `packageBodyAsStack` hands each package the artifact's whole `packages[]` as resolution context and the reference-integrity suite resolves object names through it. Graded `minor` rather than `patch` for the new observable step line, the new advisories and the newly reachable non-zero exit; ⛔ **not** declared breaking, because the narrowing could not be exhibited and is bounded by an existing gate.
20+
**Severity mapping is `os build`'s, unchanged.** A per-package `error` refuses (exit 1); an advisory joins `warnings`. So `os validate` is narrowed only to the bar the command that *ships* already holds: every input it can now refuse is one `os build` already refuses.
21+
22+
**BREAKING** — `os validate --strict` can now fail a project it passed before. Measured on a two-package fixture whose union fold is clean and whose per-package run is not (`core` owns `pp_account`; a sibling package owns the view that displays `pp_account.industry`), driving the CLI from source:
23+
24+
| `os validate` on that fixture | before | after |
25+
|---|---|---|
26+
| `--json` | warnings 0, exit 0 | warnings 1, exit 0 |
27+
| `--json --strict` | exit 0 | **exit 1** |
28+
29+
The one warning is `field-no-consumers` at `package 'com.example.ppflip.core' — object "pp_account" · field "industry"`, which `os build` already reports on the same fixture: nothing is refused here that `os build` does not already refuse, and the default (non-strict) face is unchanged in that measurement. A run that must keep its old verdict drops `--strict`; a project that wants to keep the flag fixes what the per-package pass reports, which is what `os build` has been reporting all along.
30+
31+
Why the union fold does not see it: `packageBodyAsStack` hands each package the artifact's whole `packages[]` as resolution context, so a cross-package *reference* still resolves and the reference-integrity rules stay quiet — but a reachability rule asks what the **stack** reads, and per package the stack is that one package's own body. A field whose only consumer lives in a sibling package is therefore live to the union run and inert to the per-package run, and that is the shape that reaches `--strict`.
32+
33+
Graded `minor` rather than `patch` for the new observable step line, the new advisories and the newly reachable non-zero exit; the launch window refuses `major`, so the breaking-ness is carried by the banner above and the ADR-0087 disposition below.
2134

2235
Unchanged and out of scope: the ADR-0130 D4 union fold (#17069, fixed — `authoringRuleUnionStack` is in both commands), `--json` rendering (#11727), and disagreements *within* the per-package pass's verdicts (#18204). `os lint` still runs the union pass alone; its `artifactPackages` / `packageBodyAsStack` imports serve its own intra-package duplicate-name advisory, not the shared table.
36+
37+
<!-- adr-0087: not-required (no-migration-prescription) Nothing an author writes changes: no spec key, export, config field or payload key is removed, renamed or added. What moved is which stacks one CLI command's existing rule table is run over, so `objectstack migrate meta` has nothing to rewrite and the ledger has nothing to record. -->

‎.changeset/email-template-locale-floor.md‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44

55
Email templates: say where the `en-US` fallback floor is, and report a bundle that has none.
66

7-
`IEmailService.sendTemplate` matches `(name, locale)` exactly and retries exactly one rung —
8-
the literal `en-US`. There is no language-subtag folding, so a bundle whose English row is
9-
tagged `en` is unreachable from `en-US` and from every other tag it does not itself carry;
10-
each such delivery raises `TEMPLATE_NOT_FOUND`, which classifies permanent, so it dead-letters
11-
with no retry. An app declaring `i18n.defaultLocale: 'en'` and authoring `locale: 'en'` has
12-
done the consistent thing throughout and still shipped a bundle with no floor — and it
13-
validated, built and installed clean.
7+
`IEmailService.sendTemplate` matches `(name, locale)` exactly and, for a call that NAMES a
8+
locale, retries exactly one rung — the literal `en-US` — and stops. There is no language-subtag
9+
folding, so a bundle whose English row is tagged `en` is unreachable from `en-US` and from every
10+
other tag it does not itself carry; each such delivery raises `TEMPLATE_NOT_FOUND`, which
11+
classifies permanent, so it dead-letters with no retry. An app declaring
12+
`i18n.defaultLocale: 'en'` and authoring `locale: 'en'` has done the consistent thing throughout
13+
and still shipped a bundle with no floor for those calls — and it validated, built and installed
14+
clean.
1415

1516
- `EmailTemplateDefinitionSchema.locale`'s `describe` and TSDoc now state the exact match, the
16-
single literal `en-US` rung, the absence of folding, and that the stack's own declared default
17-
locale is the wrong tag whenever it is not spelled `en-US`.
17+
one literal `en-US` rung a call that NAMES a locale gets, the absence of folding, and that the
18+
stack's own declared default locale is the wrong tag whenever it is not spelled `en-US`.
1819
- New exported `EMAIL_TEMPLATE_FLOOR_LOCALE` names that tag once: it is both the schema default
19-
and the resolver's sole retry rung.
20+
and the rung `sendTemplate` retries for a call that NAMES a locale. The full ladder — including
21+
the lowest-tag rung reachable only by a call that names NO locale — is on
22+
`SendTemplateInput.locale` in `packages/spec/src/contracts/email-service.ts`.
2023
- `defineStack` now reports (advisory `console.warn`, warn-once per bundle) an `emailTemplates`
2124
bundle that carries rows for the stack's own `i18n.supportedLocales` but none tagged `en-US`.
2225

0 commit comments

Comments
 (0)