Skip to content

02 — Vendor schema + registry (KeynoteKitProtobuf) - #27

Merged
leogdion merged 3 commits into
v0.1.xfrom
14-protobuf
Jul 29, 2026
Merged

02 — Vendor schema + registry (KeynoteKitProtobuf)#27
leogdion merged 3 commits into
v0.1.xfrom
14-protobuf

Conversation

@leogdion

Copy link
Copy Markdown
Member

Closes #14.

What landed

KeynoteKitProtobuf now carries checked-in Swift types for the vendored Keynote
15.3 schema and a Swift port of the 14.4 TSPRegistryMapping. No protoc at
consumer build time
, and no build-tool plugin.

Generation

Mirrors research/tools/prepare_keynote_parser.py exactly — 34 protos, not 33:
the 33 in research/vendor/keynote-parser/protos/15.3/ plus the 14.4
TSKArchives_sos.proto that 15.3 dropped but the registry still references, with
both directories on the include path and --swift_opt=Visibility=Public.

Generated with Homebrew protobuf 35.1 / swift-protobuf 1.38.1 — the plugin
version matches the Package.swift dependency pin exactly.

No symbol collisions across the files that share a proto package (TSCH spans
four files), so FileNaming was left at its default.

Registry

TSPRegistryMapping is modelled as [UInt32: String] and never inverted
631 identifiers name only 624 distinct messages (5/6 both
KN.SlideArchive; 7/12 both KN.PlaceholderArchive). A reverse map built
with Dictionary(uniqueKeysWithValues:) would trap at runtime.

Name → Swift metatype resolution is longest-declared-prefix, which handles both
package-qualified names (TSCH.PreUFF.ChartInfoArchive
TSCH_PreUFF_ChartInfoArchive) and the two genuinely nested messages
(TST.GroupByArchive.AggregatorArchiveTST_GroupByArchive.AggregatorArchive).

Repo-size cost (consciously accepted)

lines bytes
protoc output (34 .pb.swift) 202,036 10,665,491
registry tables (2 files) 1,285 79,810
total Generated/ 203,321 10,745,301 (~10 MB)

Whole-diff added lines: 203,630. This is the price of "no protoc on the
consumer machine" and is the intended trade.

Surprise worth recording: decoding must be partial

The 15.3 protos mark 1,497 fields required (proto2). A strict parse of an
empty or partial body throws .missingRequiredFields, so TSPRegistryMapping.decode
uses partial: true. Pairing a 15.3 schema with a 14.4 registry makes strict
enforcement actively wrong — it would reject documents Keynote itself round-trips.

Lint: excluded, not weakened

No global rule was relaxed.

  • .swiftlint.ymlappended one excluded: entry
    (Sources/KeynoteKitProtobuf/Generated). Existing entries untouched.
  • swift-format has no exclude flag and runs --recursive, so the generated
    directory carries its own .swift-format with {"rules": {}}; swift-format
    honours the nearest config. Scripts/lint.sh was not modified.
  • .periphery.yml untouched — periphery is opt-in via $PERIPHERY, disabled in
    CI, and retain_public: true already covers the generated public types.

Hand-written sources were split (TSPRegistryError into its own file, the two
test suites into their own files) so they satisfy one_declaration_per_file and
file_types_order under LINT_MODE=STRICT — which is what CI runs.

Package.swift: not modified

Zero changes — the existing swift-protobuf 1.38.1 dependency and target wiring
were sufficient. Lane S (#16) is unblocked on this file.

Verification

  • DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer xcrun swift build — green
  • ... xcrun swift test — green, 12 tests in the protobuf suites
  • LINT_MODE=STRICT ./Scripts/lint.sh — exit 0, 0 violations
  • mise run prepare-keynote-parser — still prints 631 registry entries; 0 missing message names
  • grep -c '\.plugin(' Package.swift0
  • Linux-clean: only SwiftProtobuf and canImport(FoundationEssentials)-guarded
    Foundation; no Apple-only imports, no .linkedFramework
  • research/** untouched (git status --porcelain research/ empty)

Acceptance criteria

  • Generated sources live in KeynoteKitProtobuf and are committed
  • TSPRegistryMapping ported
  • Build does not require protoc on the consumer machine
  • Every archive type named in the findings decodes from its .proto

The last is tested two ways: the 11 archive types named across
research/findings/ are decoded by name, and all 631 identifiers are decoded
via the registry with each result's protoMessageName asserted against the table
— so a transcription that paired a name with the wrong type would fail.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d2fecef-49ba-4d3c-9521-3e9efa3b2762

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@leogdion

Copy link
Copy Markdown
Member Author

Post-hoc verification

Two extra checks run after opening this PR, both green.

1. Generation is byte-for-byte reproducible. Re-running the exact protoc
invocation into a scratch directory and diffing against the committed tree:

diff -rq <fresh> Sources/KeynoteKitProtobuf/Generated \
  --exclude='.swift-format' --exclude='TSPRegistryMapping+*'
→ no differences

Toolchain: libprotoc 35.1, protoc-gen-swift 1.38.1 (matches the
Package.swift pin exactly).

2. The registry was cross-checked against protoc's own descriptor set, an
oracle that shares none of the transcription logic. Built with
--descriptor_set_out --include_imports (1,399 messages) and compared against
both committed tables:

check result
id → message-name vs mapping.py 631/631, 0 mismatches
message-name → Swift metatype vs descriptor naming 624/624, 0 mismatches

3. The tests are not vacuous. Mutating a single entry
("KN.BuildArchive": KN_BuildArchive.selfKN_SlideArchive.self) fails 4
distinct tests
, including the all-631 sweep:

Expectation failed: messageType.protoMessageName == name
Expectation failed: Swift.type(of: build).protoMessageName == "KN.BuildArchive"

The mutation was reverted; the working tree is clean.

leogdion and others added 3 commits July 29, 2026 14:21
Check in protoc-generated Swift for the 33 vendored Keynote 15.3 protos
plus the 14.4 `TSKArchives_sos.proto` the registry still references, and
port `TSPRegistryMapping` alongside them. Consumers never run `protoc`
and `Package.swift` gains no build-tool plugin.

`TSPRegistryMapping` exposes the 631-entry id -> message-name table and
resolves each name to its generated Swift metatype. The table is not
injective — 631 identifiers name 624 distinct messages — so it is modelled
as `[UInt32: String]` and never inverted.

Decoding is partial. The 15.3 protos mark 1,497 fields `required`, so a
strict parse of a real component would reject documents Keynote itself
round-trips; pairing a 15.3 schema with a 14.4 registry makes that worse.

Generated code is excluded from lint rather than the rules being relaxed:
`.swiftlint.yml` gains one `excluded:` entry and the generated directory
carries its own empty `.swift-format`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The 15.3 protos mark 1,497 fields `required`, but Keynote does not
populate all of them, so a strict proto2 parse throws
`.missingRequiredFields` on components Keynote itself round-trips.
Partial decoding is therefore correct rather than a workaround, and the
constraint applies to any archive decode — not just registry lookups —
so #17/#18 inherit it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@leogdion
leogdion merged commit 9733e93 into v0.1.x Jul 29, 2026
17 checks passed
@leogdion
leogdion deleted the 14-protobuf branch July 29, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant