atproto-lexgen does not understand "type": "space" Lexicon definitions #92

Closed
opened 2026-08-20 21:08:47 +00:00 by claude-bot · 2 comments
Collaborator

A space type declaration is a Lexicon document whose main definition has "type": "space" (see docs/spaces.md and #89). The generator does not know about it in either direction.

JSON → C#. CSharpEmitter.EmitDefs (tools/ATProtoNet.LexiconGenerator/CodeGen/CSharpEmitter.cs:97) switches on def.Type over record / object / string / token / …, and EmitPlan (CodeGen/EmitPlan.cs:116) does the same when collecting union sites. Neither has a space case, so a space-type Lexicon is silently skipped — it emits nothing and reports no error, which reads as "nothing to generate" rather than "this input is not supported".

C# → JSON. LexiconEmitter (CodeGen/LexiconEmitter.cs:140) hard-codes Type = "record", so there is no way to declare a space type from C# at all.

Diffing. LexiconDiffer (CodeGen/LexiconDiffer.cs:120) special-cases record for schema comparison. A space declaration's collections list is the interesting thing to diff — adding a collection widens every bare space: grant of that type, since the default collection set is resolved at grant-evaluation time rather than frozen at consent, so it is exactly the kind of change a diff should call out.

Suggested shape: emit a space declaration as a SpaceTypeDeclaration constant or a small partial class exposing Key, Name, LocalizedNames, and Collections — the model already exists in ATProtoNet.Spaces. At minimum, the generator should fail loudly on an unsupported main definition type instead of producing an empty file.


Filed by Claude while working on #89 (run).

A space type declaration is a Lexicon document whose `main` definition has `"type": "space"` (see `docs/spaces.md` and #89). The generator does not know about it in either direction. **JSON → C#.** `CSharpEmitter.EmitDefs` (`tools/ATProtoNet.LexiconGenerator/CodeGen/CSharpEmitter.cs:97`) switches on `def.Type` over `record` / `object` / `string` / `token` / …, and `EmitPlan` (`CodeGen/EmitPlan.cs:116`) does the same when collecting union sites. Neither has a `space` case, so a space-type Lexicon is silently skipped — it emits nothing and reports no error, which reads as "nothing to generate" rather than "this input is not supported". **C# → JSON.** `LexiconEmitter` (`CodeGen/LexiconEmitter.cs:140`) hard-codes `Type = "record"`, so there is no way to *declare* a space type from C# at all. **Diffing.** `LexiconDiffer` (`CodeGen/LexiconDiffer.cs:120`) special-cases `record` for schema comparison. A space declaration's `collections` list is the interesting thing to diff — adding a collection widens every bare `space:` grant of that type, since the default collection set is resolved at grant-evaluation time rather than frozen at consent, so it is exactly the kind of change a diff should call out. **Suggested shape:** emit a space declaration as a `SpaceTypeDeclaration` constant or a small partial class exposing `Key`, `Name`, `LocalizedNames`, and `Collections` — the model already exists in `ATProtoNet.Spaces`. At minimum, the generator should fail loudly on an unsupported `main` definition type instead of producing an empty file. --- _Filed by Claude while working on #89 ([run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/314))._
Author
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/320); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/issue-92/run-320.md) — updating every ~5 minutes while the task runs.
Author
Collaborator

Pushed claude/issue-92. Here's what I did.

atproto-lexgen now understands "type": "space" — in all three directions

JSON → C#. CSharpEmitter gained a space case. A space type is not a record — nothing is serialized against it — so it emits a static holder around the SDK's SpaceTypeDeclaration rather than a class:

public static class ForumSpace
{
    public const string Nsid = "com.atmoboards.forum";

    public static SpaceTypeDeclaration Declaration { get; } = new()
    {
        Description = "A private discussion forum.",
        Key = "any",
        Name = "AtmoBoards Forum",
        LocalizedNames = new Dictionary<string, string> { ["es"] = "Foro AtmoBoards" },
        Collections = [ "com.atmoboards.thread", "com.atmoboards.reply" ],
    };

    public static string Key => Declaration.Key;
    public static string Name => Declaration.Name;
    public static IReadOnlyDictionary<string, string>? LocalizedNames => Declaration.LocalizedNames;
    public static IReadOnlyList<string> Collections => Declaration.Collections;
}

key, name, and collections are required on that model, so a declaration missing one still emits code that compiles ("any", the raw NSID, an empty set) and a WARN names the field substituted for. DefToClassName gained a space suffix so the holder can't collide with a same-named object def.

C# → JSON. LexiconEmitter hard-coded Type = "record". It now also scans for any public static SpaceTypeDeclaration and takes the NSID from a sibling Nsid (or SpaceType) constant — exactly the shape the csharp command emits, so a declaration round-trips. It gained a Warnings list (surfaced as WARN by the CLI) for a declaration with no NSID to attribute it to, and for two types claiming the same definition, which previously clobbered silently.

Diffing. LexiconDiffer compares the declaration, with the collection list treated as the load-bearing part, per the reasoning in the issue:

  com.atmoboards.forum
    [  OK ] Collection 'com.atmoboards.attachment' added — widens every bare 'space:com.atmoboards.forum' grant, including ones already consented to [main.collections]
    [BREAK] Collection 'com.atmoboards.reply' removed — narrows every bare 'space:com.atmoboards.forum' grant, so clients already writing it lose access [main.collections]
    [  OK ] Space name changed from 'AtmoBoards Forum' to 'AtmoBoards Forums' (shown on OAuth consent screens) [main.name]
    [  OK ] Localized name for 'de' removed — consent screens fall back to 'AtmoBoards Forums' [main.name:lang.de]

Key changes are breaking; name and localizations are not. Four new ChangeKind values.

Failing loudly. An unrecognized definition type is now a WARN naming the NSID and the type instead of an empty file. I scoped this to types outside the Lexicon vocabulary — query/procedure/subscription and the scalar defs stay silent, since those are legitimate input that deliberately emits no standalone C#, and warning on them would bury real diagnostics under hundreds of lines against any real schema set.

EmitPlan's union-site switch needs no space case (a declaration's fields are scalars and NSID lists, so it hosts no union sites) — I left a comment there saying so, since the omission otherwise reads as the bug it was next to.

Verification

  • Full unit suite: 1528 passed, 0 failed (5 pre-existing Aspire manifest skips). 17 new tests across the three directions.
  • The generated code actually compiles. I ran the tool over four fixture Lexicons, dropped the output into a scratch project referencing src/ATProtoNet with TreatWarningsAsErrors=true, built it clean, and ran it — ForumSpace.Declaration.GetName("es")Foro AtmoBoards, AtProtoScopes.Space(ForumSpace.Nsid)space:com.atmoboards.forum.
  • Round-trip. Feeding that compiled assembly back through atproto-lexgen lexicon reproduced the input document, name:lang and all.
  • Docs: new Space type declarations section in docs/lexicon-codegen.md, the diff and type tables updated, a pointer from docs/spaces.md, CLI help text, and a CHANGELOG.md entry under [Unreleased] / Added.

No follow-up issues to file.


⏱ 12m 37s · 91 turns · tokens: 164.9k in / 52.4k out (+10536.2k cache-read)
📜 Full session transcript
→ Opened PR: #96

Pushed `claude/issue-92`. Here's what I did. ## `atproto-lexgen` now understands `"type": "space"` — in all three directions **JSON → C#.** `CSharpEmitter` gained a `space` case. A space type is not a record — nothing is serialized against it — so it emits a static holder around the SDK's `SpaceTypeDeclaration` rather than a class: ```csharp public static class ForumSpace { public const string Nsid = "com.atmoboards.forum"; public static SpaceTypeDeclaration Declaration { get; } = new() { Description = "A private discussion forum.", Key = "any", Name = "AtmoBoards Forum", LocalizedNames = new Dictionary<string, string> { ["es"] = "Foro AtmoBoards" }, Collections = [ "com.atmoboards.thread", "com.atmoboards.reply" ], }; public static string Key => Declaration.Key; public static string Name => Declaration.Name; public static IReadOnlyDictionary<string, string>? LocalizedNames => Declaration.LocalizedNames; public static IReadOnlyList<string> Collections => Declaration.Collections; } ``` `key`, `name`, and `collections` are `required` on that model, so a declaration missing one still emits code that compiles (`"any"`, the raw NSID, an empty set) and a `WARN` names the field substituted for. `DefToClassName` gained a `space` suffix so the holder can't collide with a same-named object def. **C# → JSON.** `LexiconEmitter` hard-coded `Type = "record"`. It now also scans for any public static `SpaceTypeDeclaration` and takes the NSID from a sibling `Nsid` (or `SpaceType`) constant — exactly the shape the `csharp` command emits, so a declaration round-trips. It gained a `Warnings` list (surfaced as `WARN` by the CLI) for a declaration with no NSID to attribute it to, and for two types claiming the same definition, which previously clobbered silently. **Diffing.** `LexiconDiffer` compares the declaration, with the collection list treated as the load-bearing part, per the reasoning in the issue: ``` com.atmoboards.forum [ OK ] Collection 'com.atmoboards.attachment' added — widens every bare 'space:com.atmoboards.forum' grant, including ones already consented to [main.collections] [BREAK] Collection 'com.atmoboards.reply' removed — narrows every bare 'space:com.atmoboards.forum' grant, so clients already writing it lose access [main.collections] [ OK ] Space name changed from 'AtmoBoards Forum' to 'AtmoBoards Forums' (shown on OAuth consent screens) [main.name] [ OK ] Localized name for 'de' removed — consent screens fall back to 'AtmoBoards Forums' [main.name:lang.de] ``` Key changes are breaking; name and localizations are not. Four new `ChangeKind` values. **Failing loudly.** An unrecognized definition type is now a `WARN` naming the NSID and the type instead of an empty file. I scoped this to types outside the Lexicon vocabulary — `query`/`procedure`/`subscription` and the scalar defs stay silent, since those are legitimate input that deliberately emits no standalone C#, and warning on them would bury real diagnostics under hundreds of lines against any real schema set. `EmitPlan`'s union-site switch needs no `space` case (a declaration's fields are scalars and NSID lists, so it hosts no union sites) — I left a comment there saying so, since the omission otherwise reads as the bug it was next to. ## Verification - **Full unit suite: 1528 passed, 0 failed** (5 pre-existing Aspire manifest skips). 17 new tests across the three directions. - **The generated code actually compiles.** I ran the tool over four fixture Lexicons, dropped the output into a scratch project referencing `src/ATProtoNet` with `TreatWarningsAsErrors=true`, built it clean, and ran it — `ForumSpace.Declaration.GetName("es")` → `Foro AtmoBoards`, `AtProtoScopes.Space(ForumSpace.Nsid)` → `space:com.atmoboards.forum`. - **Round-trip.** Feeding that compiled assembly back through `atproto-lexgen lexicon` reproduced the input document, `name:lang` and all. - Docs: new *Space type declarations* section in `docs/lexicon-codegen.md`, the diff and type tables updated, a pointer from `docs/spaces.md`, CLI help text, and a `CHANGELOG.md` entry under `[Unreleased] / Added`. No follow-up issues to file. --- ⏱ 12m 37s · 91 turns · tokens: 164.9k in / 52.4k out (+10536.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/ATProto.NET/issue-92/run-320.md) → Opened PR: https://git.grandiras.net/Grandiras/ATProto.NET/pulls/96
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/ATProto.NET#92
No description provided.