Skip to content

Activity transcripts: migrate the 21 remaining unkeyed LogMessage sites, and mirror the 41 activity.* keys #3281

Closed

Description

@rbuergi

Follow-up to #3236, which landed the seam (LogMessage.MessageKey / MessageArgs,
LocalizationCatalog.GetNamed, message.Localize(locale) / host.Localize(message)) plus 37 of 58
migrated write sites and the UnkeyedActivityLogMessageRatchetGuard inventory.

What is left, and why each is where it is

test/UnkeyedActivityLogMessages.allow carries the 21 remaining unkeyed new LogMessage( sites
under src/. They split three ways, and only the first group is actual work.

1. MIGRATABLE — needs a key threaded through a public signature (9 sites)

File sites what it needs
MeshWeaver.GitSync/ActivityRunner.cs 3 The activity TITLE, each step's message and the terminal finalMessage arrive as string parameters of RunActivity / Log / Finish. The eight callers in GitHubActivityExtensions compose real sentences ("Commit {space} to GitHub", "Re-import {space} at {commitish}", "Open pull request", …), so these ARE translatable — they need an optional key parameter (or a LogMessage overload) on the runner.
MeshWeaver.Compiler.Pipeline/NodeTypeCompilationActivity.cs 2 AppendLog(…, string message, …) and Update(…, string? error, …). The batched AppendLogs(…, LogMessage[] …) already accepts a keyed message, so the migration is at the ~40 CALLERS of AppendLog, one sentence at a time — no signature change here.
MeshWeaver.Compiler.Pipeline/MeshNodeCompilationService.cs 3 AppendInfo / AppendWarning / AppendError(log, string message). Same shape: migrate the callers, or give the helpers a key parameter.
MeshWeaver.Graph/StaticRepoImporter.cs 1 of 4 The content-sync ledger's string message parameter.

The two composed import SUMMARIES in StaticRepoImporter (the lock stamp and the terminal line) are
a judgement call rather than a mechanical migration: each folds 6+ conditional notes carrying #604 /
#3101 incident wording. Keying them well means keying each note and composing the translated pieces —
worth doing, but it is its own change with its own review.

2. GENERIC PLUMBING — nothing to key at the site

An ILogger → activity adapter (ActivityLogLogger, Activity.Log<TState>) or ActivityLog.Fail:
the text arrives already formatted. A caller that wants a key builds the LogMessage itself.

3. VERBATIM UPSTREAM TEXT — no catalog can carry it

ex.Message, a Roslyn diagnostic, settle.Diagnosis, a descendant hub's own refusal, the
stranded-instances report. These stay English by the same rule as the Describe* family
(Doc/Architecture/Localization → "viewer message vs. owner diagnostic"), and where such text is
spliced into a sentence the platform owns, the lead is already keyed and the detail rides as a
{detail} argument.

Also outstanding

  • The MeshWeaver.Plugins client mirror. ActivityLog messages are persisted English strings — a rendered surface that cannot be localized (53 sites, 0 localizable) #3236 added 41 activity.* keys to
    src/MeshWeaver.Messaging.Hub/Localization/strings.{en,de}.json. Their second home is
    MeshWeaver.Plugins/clients/react/src/i18n/strings.{en,de}.json, whose drift guard compares
    VALUES; core merged first, so that repo's RN app + web clients (typecheck + test) job is red
    until the mirror lands.
  • The AI engine's activity views. They ship in MeshWeaver.Plugins and still render
    m.Message. Switching them to host.Localize(m) / m.Localize(locale) is safe on every row
    (an unkeyed entry renders its stored English unchanged) and is what makes the migrated keys
    actually visible to a German viewer in the surface most people read.

The census trap, recorded because it cost the original issue three sites

grep 'new LogMessage(' does not see a target-typed Messages = [new("…", level)]. Three such sites
in LayoutClientExtensions were missing from #3236's 53. They are now spelled out and
UnkeyedActivityLogMessageRatchetGuard.NoTargetTypedLogMessageConstructionHidesFromTheRatchet refuses
the shape outright, so the ratchet's inventory is complete rather than merely consistent.

Activity

  1. rbuergi commented on Sep 4, 2026

    @rbuergi
    ContributorAuthor

    🚨 Correction to this issue's "Also outstanding → Plugins client mirror" bullet, measured
    2026-09-04 in the plugins checkout.

    Adding keys in core does not red the plugins repo. clients/react/src/i18n/localize.test.ts
    compares the client catalog against a PINNED core commit recorded in
    clients/react/src/i18n/catalog-source.json, not against core's main — pinned deliberately in
    MeshWeaver.Plugins#893, because core merges faster than a Plugins CI cycle and an unpinned guard
    could not converge (it reddened every unrelated PR on a subsystem its diff could not reach).

    catalog-source.json  ref e7f1d699e563b54cc57875b176116d75b71398a3 (2026-09-03)
                         keys en 1104 / de 1104
    core main            1174 keys at that moment
    

    Seventy keys behind, every gate green in both repos. So the real failure mode is the opposite of
    "the mirror goes red": it goes silently stale, and nothing anywhere will remind anyone. The sync
    is a deliberate step against the merged core sha:

    cd clients/react && npm run sync:i18n -- --ref <merged core sha>

    which rewrites both strings.{en,de}.json, the ref and the recorded key counts together.

    MeshWeaver#3282 corrects this in all three guidance homes (AGENTS.md, the /i18n skill,
    Doc/Architecture/Localization).

  2. rbuergi commented on Sep 4, 2026

    @rbuergi
    ContributorAuthor

    The mirror half is done — and the issue's premise about it was wrong

    The MeshWeaver.Plugins client mirror. … core merged first, so that repo's RN app + web clients (typecheck + test) job is red until the mirror lands.

    It was not red. Measured today before touching anything:

    pin keys
    mirror (clients/react/src/i18n/catalog-source.json) e7f1d699e (2026-09-03T08:05Z) 1104
    core main f9d2a335a 1283

    The drift guard compares the mirror against its own pin, never against core's default branch — so
    a key added in core reddens nothing and the mirror goes silently stale instead. 179 strings were
    rendering English for a German viewer of the React and RN clients, with both repos green for ~30 hours.

    Landed as Systemorph/MeshWeaver.Plugins#1332 (e7f1d699e -> f9d2a335a, en/de 1283 each). Of the 179
    added keys only 7 equal their English value, all deliberate: Embeddings, Registry, Plugins,
    Provider, Thread (glossary terms kept English by policy), Plan: {0} (identical in German) and
    '{path}': {warning} (pure interpolation).

    Worth adding to this issue's scope

    The guard cannot see this class of drift by construction — it asks "does the mirror match its
    pin"
    , never "is the pin current". That is the same shape as a gate that tests its own inputs: it
    passes having checked something real, while the thing you care about is unobserved. A syncing PR fixes
    today's drift and restores the same blind spot for tomorrow.

    A check that fails when the pin falls behind core's default branch by more than N keys or M days would
    close it. Until then the sync happens whenever someone happens to look, which is what produced 179.

    Still open on this issue

    The 9 migratable LogMessage sites (group 1) and the AI engine's activity views in Plugins are
    untouched by the above.

  3. rbuergi commented on Sep 4, 2026

    @rbuergi
    ContributorAuthor

    Group 1 is migrated — MeshWeaver#3305

    The ratchet's inventory drops 21 → 18, and every line left in it is there because it cannot
    carry a key rather than because nobody got to it. 56 keys added to strings.{en,de}.json.

    Migrated, per the table in this issue:

    File Was Now
    MeshWeaver.GitSync/ActivityRunner.cs 3 3 — but all three are now the deliberate un-keyed spellings, not a to-do
    MeshWeaver.Compiler.Pipeline/MeshNodeCompilationService.cs 3 1
    MeshWeaver.Graph/StaticRepoImporter.cs 4 3
    MeshWeaver.Compiler.Pipeline/NodeTypeCompilationActivity.cs 2 2 — unchanged by design; its three callers moved to AppendLogs

    The issue's prediction about AppendLog held exactly: no signature change was needed there.

    Three notes worth keeping, because they are the shape any future migration takes:

    • A required key beats an optional one. Append{Info,Warning,Error} now require a key. An
      optional parameter leaves the helper looking migrated while the next caller silently re-opens the
      hole, and the ratchet cannot see past a helper to the callers behind it. The one caller that
      genuinely cannot supply a key (Roslyn's own diagnostics) calls a separately named
      AppendVerbatimWarning — so the un-keyed case is a decision someone wrote down.
    • A conditional clause gets its own key, never a {suffix} argument. ", repository created"
      spliced into a translated sentence is untranslated English inside German word order.
    • Chain .WithKey onto the constructor. The ratchet caught me building a LogMessage on one
      line and chaining on the next — correctly, because English and key are then free to drift.

    The two "Also outstanding" items — both measured, both blocked

    Neither is deferred by choice, so #3305 deliberately does not carry a Closes.

    1. The client mirror is blocked on #3305's merge sha, since the sync takes it as an argument
    (npm run sync:i18n -- --ref <sha>). Nothing red will remind anyone — the drift guard compares
    against a pinned core commit — which is why it is written into #3305's body as an explicit hand-over.

    2. The activity views are blocked on a core PIN BUMP, not on effort. Three Plugins surfaces still
    render the raw English:

    File What it renders
    MeshWeaver.Blazor/Components/CompileProgressIndicator.razor.cs msgs[^1].Message
    MeshWeaver.ContentCollections.Indexing.Graph/ContentIndexSettingsTab.cs Esc(m.Message)
    MeshWeaver.Blazor.EntityViews/EditFormView.razor @logMessage — the record's ToString(), so this one is a display bug today, independent of language

    The fix is m.Localize(viewerLocale) and it cannot compile there yet. Measured against
    MeshWeaver.Plugins' MW_PLATFORM_REF = b88be8a (2026-09-03):
    src/MeshWeaver.Data/LogMessageLocalizationExtensions.cs — the Localize seam #3236 introduced —
    does not exist at that commit. So this half follows the pin.

    Core's own GitSync renderer is already wired (GitHubSyncSettingsTab.ActivityHtml calls
    m.Localize(host.ViewerLocale())), so the GitSync transcript is live in German the moment #3305
    merges — it is the compile-progress and content-index surfaces that wait.

    One thing this turned up that is not in the issue

    Adding the RunActivity(…, LogMessage title, …) overload makes an unqualified
    <see cref="ActivityRunner.RunActivity"/> CS0419 in any dependent — an error under
    -warnaserror. That is break shape 3 in CrossRepoPairGate.md: no gate sees it, and because
    MeshWeaver.Plugins pins core it would not have gone red on the merge, but on whichever later PR
    moved the pin. Landed ahead of core as Systemorph/MeshWeaver.Plugins#1338.

    🤖 Filed from Claude Code.

  4. rbuergi commented on Sep 4, 2026

    @rbuergi
    ContributorAuthor

    Merged — MeshWeaver#3305 is 59352715f7f4f338bd76b1293f1d03de5760b024 (14:21Z)

    Group 1 is done: ratchet 21 → 18, 56 keys in both catalogs, and the GitSync transcript renders in
    the viewer's language from now on (core's own GitHubSyncSettingsTab.ActivityHtml already resolves
    host.ViewerLocale()).

    This issue stays open deliberately, with two items whose blockers are now measured rather than
    assumed. Both are in MeshWeaver.Plugins.

    1. The client mirror — handed over, ref available now

    The merge sha above is the argument the sync needs:

    npm run sync:i18n -- --ref 59352715f7f4f338bd76b1293f1d03de5760b024
    

    Systemorph/MeshWeaver.Plugins#1337 is already open syncing the mirror, but it was cut before this
    merge — it says "1301/1301, zero drift" while core is now at 1357. Noted on that PR. Nothing will
    go red about it: the guard compares against the pinned commit in catalog-source.json, not core main.

    2. The activity renderers — blocked on the pin, and here is the check

    Three surfaces still render the raw English:

    File What it renders
    MeshWeaver.Blazor/Components/CompileProgressIndicator.razor.cs msgs[^1].Message
    MeshWeaver.ContentCollections.Indexing.Graph/ContentIndexSettingsTab.cs Esc(m.Message)
    MeshWeaver.Blazor.EntityViews/EditFormView.razor @logMessage — the record's ToString(), so this one prints LogMessage { Message = …, LogLevel = … } today, in any language

    The fix is m.Localize(viewerLocale) and it cannot compile there yet. Measured, not inferred:

    $ git cat-file -e b88be8a05:src/MeshWeaver.Data/LogMessageLocalizationExtensions.cs
    ABSENT
    

    b88be8a is MeshWeaver.Plugins' MW_PLATFORM_REF (2026-09-03T17:57Z); the Localize seam #3236
    introduced does not exist at it. So this half follows a pin bump, and #1333 / #1331 are the ones
    moving it.

    Worth keeping as a rule: for a dependent that pins core, check the API is present at the pin
    (git cat-file -e <pin>:<path>) before planning the dependent half. git log by date answers a
    different question.

    One thing this turned up that was not in the issue

    Adding RunActivity(…, LogMessage title, …) makes an unqualified <see cref="ActivityRunner.RunActivity"/>
    CS0419 in any dependent — an error under -warnaserror. Break shape 3 in CrossRepoPairGate.md:
    no gate sees it, and since MeshWeaver.Plugins pins core it would not have reddened on this merge but
    on whichever later PR moved the pin. Systemorph/MeshWeaver.Plugins#1338 fixes the three affected
    crefs; a signature-qualified cref resolves against one overload as well as two, so it is correct both
    before and after. It must land before any pin bump that carries core past 5935271 — #1333 /
    #1331 both target earlier core commits, so they are unaffected as they stand.

    🤖 Filed from Claude Code.

  5. rbuergi commented on Sep 4, 2026

    @rbuergi
    ContributorAuthor

    Correction: the renderer half is UNBLOCKED as of Plugins#1333

    My earlier comment said the three Plugins renderers could not compile because the Localize seam was
    absent at that repo's MW_PLATFORM_REF. That was true when measured and is no longer true. The
    blocker was removed by the pin move, not by anything on this issue.

    Authoritative re-measurement, via git ls-tree (which prints the path or nothing, and so cannot
    confuse "absent" with "bad sha"):

    $ git ls-tree --name-only <sha> -- src/MeshWeaver.Data/LogMessageLocalizationExtensions.cs
    b88be8a05  → (nothing)                                              ← ABSENT
    7d644de95  → src/MeshWeaver.Data/LogMessageLocalizationExtensions.cs ← PRESENT
    59352715f  → src/MeshWeaver.Data/LogMessageLocalizationExtensions.cs ← PRESENT
    
    • b88be8a05 was MW_PLATFORM_REF when I measured, so the verdict was correct then.
    • Systemorph/MeshWeaver.Plugins#1333 moved MW_PLATFORM_REF to 7d644de95 at 21:48Z, where the seam
      is present.

    So m.Localize(viewerLocale) compiles against the current pin today, and this half needs no pin
    bump and no dependency on #1338.

    Two things worth recording, because both nearly cost more than the fix

    1. A "blocked on the pin" verdict has a shelf life of one pin bump. Nothing on this issue changed
    when the pin moved; the blocker simply expired silently. Any such verdict should name the sha it was
    measured against — and be re-measured before it is repeated. Mine did name the sha, which is the only
    reason the expiry was noticeable at all.

    2. git cat-file -e "$sha:$path" && echo PRESENT || echo ABSENT is not a safe check. It exits
    non-zero for an absent path, a bad sha, a missing object and bad quoting alike — one word covering
    several states. On re-checking, that idiom handed me a false PRESENT for b88be8a05 and briefly
    convinced me my original, correct measurement had been wrong. git ls-tree answers with the path or
    with silence and cannot be confused. (This is the shape written up in
    Controls That Cannot Fail; it is mildly embarrassing to
    have hit it inside the same day, which is rather the point of that page.)

    Plugins carries four core-ish refs and they are not interchangeable: uses:@sha (workflow
    definition), MW_PLATFORM_REF (the core commit src/ compiles against), MW_PLATFORM_SET /
    MW_IMAGE_DIGEST (the platform image), and clients/react/src/i18n/catalog-source.json ref (the
    i18n mirror's baseline — not a build pin at all). They held the same value earlier on 2026-09-04
    and diverged at 21:48Z, so "which pin?" is a real question even when the answer looks obvious.

    Where that leaves this issue

    • Core (group 1) — done, i18n: key the activity transcripts a viewer actually reads (#3281) #3305 / 59352715f.
    • Renderers — now actionable, not blocked. CompileProgressIndicator (msgs[^1].Message),
      ContentIndexSettingsTab (Esc(m.Message)), EditFormView.razor (@logMessage, which renders the
      record's ToString() and is a display bug in any language).
    • Mirror sync — still needs npm run sync:i18n -- --ref 59352715f7f4f338bd76b1293f1d03de5760b024;
      note catalog-source.json still records b88be8a05, so the mirror is now behind on two counts.

    🤖 Filed from Claude Code.

  6. rbuergi commented on Sep 5, 2026

    @rbuergi
    ContributorAuthor

    Complete — closing

    All three parts are merged and verified on main in both repos, not merely merged:

    Part Where Verified on main
    Core migration — the 21 remaining unkeyed sites MeshWeaver#3305 (59352715f) ratchet 21 → 18, 56 keys
    The three activity renderers Plugins#1348 Localize present in all three files
    The client i18n mirror Plugins#1350 ref=e02801615, en 1365 / de 1365
    (cross-repo counterpart) Plugins#1338 signature-qualified cref present

    A German viewer now reads the GitSync transcript, the compile pipeline's progress and failure lines,
    the import's progress and content-sync ledger, and the compile-progress tail under a compiling
    NodeType — in German. Every English fallback is character-for-character what it was, so rows written
    before #3236 and any un-migrated writer render exactly as they did.

    EditFormView.razor turned out to carry a plain display bug independent of language: it rendered the
    LogMessage record, so a validation error printed LogMessage { Message = …, LogLevel = …, Timestamp = … } to every viewer, presumably for as long as that block existed.

    Three things worth keeping, all of which cost time here

    1. A "blocked at the pin" verdict has a shelf life of one pin bump. The renderer half was
    correctly measured as unbuildable (LogMessageLocalizationExtensions absent at b88be8a05), and that
    verdict expired silently hours later when Plugins#1333 moved MW_PLATFORM_REF to 7d644de95, where
    the seam exists. Nothing announced it. Name the sha a blocker is measured against, and re-measure
    before repeating it.

    2. git cat-file -e "$sha:$path" && echo PRESENT || echo ABSENT is not a safe check — it exits
    non-zero for an absent path, a bad sha, a missing object and bad quoting alike, and it produced a
    false PRESENT here. git ls-tree --name-only <sha> -- <path> prints the path or prints nothing.

    3. The mirror goes stale silently, and its own sync can be stale on arrival. #1337 synced earlier
    the same day but was cut before #3305 merged, recording 1301/1301 while core was at 1365 — and
    nothing goes red, because the guard compares against the pinned commit, not core main. #1350's
    sync was verified against core by an independent reader (0 missing, 0 extra, 0 value-diffs, both
    languages) rather than by the script's own success line.

    All three are written up in Doc/Architecture/ControlsThatCannotFail and Doc/Architecture/Localization.

    🤖 Filed from Claude Code.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions