epic: every builtin language becomes a first-party plugin package #309

Open
opened 2026-09-27 15:03:26 +02:00 by buildagent · 0 comments
Member

Follows #75 (runtime plugin architecture). Goal: the base system is infrastructure only, and each of the seven languages (Ruby, PHP, JS, TS, C#, Rust, Python) ships as a first-party plugin package. A language fix can then be released without a core release.

Status as of 2026-09-27: Phase 0 and Phase 1 are merged (master 4ecf930). No language has flipped yet; the builtin parsers still index everything.

Operator decisions (2026-09-26)

  1. Ids: first-party packages use RESERVED PLAIN ids (ruby, php, …). No migration of rows, configs or baselines. Third-party packages may not claim them.
  2. Enablement: first-party packages ship in the release archive and are OPT-IN.
    • code-index init, or the first start with no decision, enables the languages the repository contains and says so.
    • An automatic start records the decision in .code-index/languages.toml, never in the project root.
    • Upgrades keep every language the index already holds.
  3. Perf budget per port, against the builtin on the pinned corpus: at most 10% slower warm and 20% slower cold. No regression for users without packages.
  4. Fallback: the builtin stays for ONE release after a flip, switchable with [languages] <lang> = builtin|package, and is deleted in N+1.
  5. Port order, one per release: Ruby → PHP → JS → TS → C# → Rust → Python.

Premise corrections from planning

  • Most language logic is in the resolver, not the extractors. 23 of the 26 language fixes since v0.32.0 touched the resolver. The per-language resolver rules have to become package-declared data: profiles, a module_map hook and new facts.
  • The real cost blocker is the general influence pass, which adds about 80% SQLite work once any package holds capabilities. First-party packages are now exempt.
  • The earlier Ruby overhead figures (1.44× vs about 3.4×) are both correct: they were measured before and after #113. See _prdoc/records/P1-ruby-package-overhead.md.

Phase 0 — DONE (#306, master 0bcdac1)

  • Profile routing: every inline builtin language id is routed through lang_profile. There were 57 literals at 41 sites; now 0, enforced by lang_literal_gate with no allowlist. The resolver SQL reads temp.lang_profile.
  • Language filters: a lang filter given as a profile name (lang: "ruby") also matches packaged languages.
  • Real-grammar costs: _prdoc/records/P0-real-grammar-costs.md. Workers set oom_score_adj=1000, precompile runs in parallel, idle workers retire after 60 s.
  • Ruby fixes: the W0 local-decisions fixture, and a transient package refusal now keeps the file's facts (W7).
  • Gates: a parameterized grammar build (with the PHP wchar.h shim), a generic package_parity LangSpec harness, and COSI_CORPUS_LANGS.
  • Packaged C# isolation: packaged C# is isolated from builtin C# in the partial-class and reclassification tiers, which now key on lang and take their capability gates.

Phase 1 — DONE (#308, master 4ecf930)

  • Reserved plain ids and language_reserved: first-party rows are stamped with the plain id.
  • Owner setting: [languages] <lang> = auto|builtin|package. A switch re-extracts only the files that change hands and is disclosed.
  • Opt-in enablement: code-index languages list|enable|owner, the language_not_enabled disclosure, and the upgrade rule.
  • Declared profiles: [languages.profile] with extends and closed enums. The first_party_profiles gate checks each builtin's declaration against its compiled answers.
  • ABI tags: doc span 0x8005, qualifier segments 0x8006, exports 0x8007, grantable lexical_local/pytest_fixture, extension_methods, and multi-grammar [[grammars]] lanes.
  • module_map hook: host side, plus Composer PSR-4 in code-index-guest-kit. It agrees with the builtin on 129 of 129 comparable php-guzzle files.
  • code-index-guest-kit: a native Ruby differential over ruby-sinatra (152 files) finds 0 differences.
  • Influence-pass exemption for compiled-in first-party keys: SQLite work drops from 1.76× to 1.00× the builtin. An operator's --first-party anchor grants reserved-id permission only, not the exemption.
  • HOST_EXTRACTION_EPOCH (m0072/m0073), now 2, with a fingerprint gate over every shipped package's derived rows.
  • Verified caches: grammar and extractor caches, keyed per lane by the wasm's content hash, with garbage collection. Loading the Ruby grammar drops from 215 ms to 24 ms, and the pool grows lazily.

Remaining before the FIRST flip (Ruby)

  • #301 resumable resolve. It also causes the nightly query_cli flake on master.
  • #307 promotion lock at 100k files: 13.9 s, 89.9% of it re-stamping carried rows.
  • Move the declared-profile table (created on first use) into a numbered migration; the package baseline must then be re-measured.
  • Re-measure the cold budget on a quiet machine: it is met on wall time, measured on a contended machine.
  • Named grammar lanes do not use the grammar cache yet.

Phase 2 — distribution (not started)

  • Bundle the first-party packages in the release archive, with an enabled language mapped to its bundled package.
  • Independent package releases: package tags, a rolling signed catalog, plugin update <id>, and a requires_host range. The reusable release workflow from Phase 0 is designed but not built, and must be verified by dispatching it.

Phase 3 — flips, one per release

  • Ruby: first a shadow release in which the package ships and the builtin still owns the language; then the flip; then delete the builtin one release later.
  • PHP: needs the resolver to consume module_map (PSR-4).
  • JS, TS: need multi-grammar lanes to use the grammar cache.
  • C#.
  • Rust.
  • Python: needs the resolver to consume the exports fact (__all__) and module_map (src layouts).

Known and not fixed

  • pc_calls chooses the enclosing class from ungated symbols. This only matters with two producers in one file, which no shipped path creates yet.
  • bounding_site_registry matches bounds by name across the workspace, so a reused name inherits another bound's disclosure.
  • corpus_require_floor greps --test <name> without checking which package it belongs to.
  • Lane worktrees are only indexed when COSI_WORKTREES_DIR is set (#238), and our own .mcp.json did not set it until now.
Follows #75 (runtime plugin architecture). **Goal:** the base system is infrastructure only, and each of the seven languages (Ruby, PHP, JS, TS, C#, Rust, Python) ships as a first-party plugin package. A language fix can then be released without a core release. **Status as of 2026-09-27:** Phase 0 and Phase 1 are merged (master `4ecf930`). No language has flipped yet; the builtin parsers still index everything. ## Operator decisions (2026-09-26) 1. **Ids:** first-party packages use RESERVED PLAIN ids (`ruby`, `php`, …). No migration of rows, configs or baselines. Third-party packages may not claim them. 2. **Enablement:** first-party packages ship in the release archive and are OPT-IN. - `code-index init`, or the first start with no decision, enables the languages the repository contains and says so. - An automatic start records the decision in `.code-index/languages.toml`, never in the project root. - Upgrades keep every language the index already holds. 3. **Perf budget per port**, against the builtin on the pinned corpus: at most 10% slower warm and 20% slower cold. No regression for users without packages. 4. **Fallback:** the builtin stays for ONE release after a flip, switchable with `[languages] <lang> = builtin|package`, and is deleted in N+1. 5. **Port order,** one per release: Ruby → PHP → JS → TS → C# → Rust → Python. ## Premise corrections from planning - **Most language logic is in the resolver, not the extractors.** 23 of the 26 language fixes since v0.32.0 touched the resolver. The per-language resolver rules have to become package-declared data: profiles, a `module_map` hook and new facts. - **The real cost blocker is the general influence pass,** which adds about 80% SQLite work once any package holds capabilities. First-party packages are now exempt. - **The earlier Ruby overhead figures (1.44× vs about 3.4×) are both correct:** they were measured before and after #113. See `_prdoc/records/P1-ruby-package-overhead.md`. ## Phase 0 — DONE (#306, master `0bcdac1`) - [x] **Profile routing:** every inline builtin language id is routed through `lang_profile`. There were 57 literals at 41 sites; now 0, enforced by `lang_literal_gate` with no allowlist. The resolver SQL reads `temp.lang_profile`. - [x] **Language filters:** a `lang` filter given as a profile name (`lang: "ruby"`) also matches packaged languages. - [x] **Real-grammar costs:** `_prdoc/records/P0-real-grammar-costs.md`. Workers set `oom_score_adj=1000`, precompile runs in parallel, idle workers retire after 60 s. - [x] **Ruby fixes:** the W0 local-decisions fixture, and a transient package refusal now keeps the file's facts (W7). - [x] **Gates:** a parameterized grammar build (with the PHP `wchar.h` shim), a generic `package_parity` `LangSpec` harness, and `COSI_CORPUS_LANGS`. - [x] **Packaged C# isolation:** packaged C# is isolated from builtin C# in the partial-class and reclassification tiers, which now key on `lang` and take their capability gates. ## Phase 1 — DONE (#308, master `4ecf930`) - [x] **Reserved plain ids and `language_reserved`:** first-party rows are stamped with the plain id. - [x] **Owner setting:** `[languages] <lang> = auto|builtin|package`. A switch re-extracts only the files that change hands and is disclosed. - [x] **Opt-in enablement:** `code-index languages list|enable|owner`, the `language_not_enabled` disclosure, and the upgrade rule. - [x] **Declared profiles:** `[languages.profile]` with `extends` and closed enums. The `first_party_profiles` gate checks each builtin's declaration against its compiled answers. - [x] **ABI tags:** doc span `0x8005`, qualifier segments `0x8006`, exports `0x8007`, grantable `lexical_local`/`pytest_fixture`, `extension_methods`, and multi-grammar `[[grammars]]` lanes. - [x] **`module_map` hook:** host side, plus Composer PSR-4 in `code-index-guest-kit`. It agrees with the builtin on 129 of 129 comparable `php-guzzle` files. - [x] **`code-index-guest-kit`:** a native Ruby differential over ruby-sinatra (152 files) finds 0 differences. - [x] **Influence-pass exemption** for compiled-in first-party keys: SQLite work drops from 1.76× to 1.00× the builtin. An operator's `--first-party` anchor grants reserved-id permission only, not the exemption. - [x] **`HOST_EXTRACTION_EPOCH`** (m0072/m0073), now 2, with a fingerprint gate over every shipped package's derived rows. - [x] **Verified caches:** grammar and extractor caches, keyed per lane by the wasm's content hash, with garbage collection. Loading the Ruby grammar drops from 215 ms to 24 ms, and the pool grows lazily. ## Remaining before the FIRST flip (Ruby) - [ ] #301 resumable resolve. It also causes the nightly `query_cli` flake on master. - [ ] #307 promotion lock at 100k files: 13.9 s, 89.9% of it re-stamping carried rows. - [ ] Move the declared-profile table (created on first use) into a numbered migration; the package baseline must then be re-measured. - [ ] Re-measure the cold budget on a quiet machine: it is met on wall time, measured on a contended machine. - [ ] Named grammar lanes do not use the grammar cache yet. ## Phase 2 — distribution (not started) - [ ] Bundle the first-party packages in the release archive, with an enabled language mapped to its bundled package. - [ ] Independent package releases: package tags, a rolling signed catalog, `plugin update <id>`, and a `requires_host` range. The reusable release workflow from Phase 0 is designed but not built, and must be verified by dispatching it. ## Phase 3 — flips, one per release - [ ] **Ruby:** first a shadow release in which the package ships and the builtin still owns the language; then the flip; then delete the builtin one release later. - [ ] **PHP:** needs the resolver to consume `module_map` (PSR-4). - [ ] **JS, TS:** need multi-grammar lanes to use the grammar cache. - [ ] **C#.** - [ ] **Rust.** - [ ] **Python:** needs the resolver to consume the exports fact (`__all__`) and `module_map` (src layouts). ## Known and not fixed - `pc_calls` chooses the enclosing class from ungated symbols. This only matters with two producers in one file, which no shipped path creates yet. - `bounding_site_registry` matches bounds by name across the workspace, so a reused name inherits another bound's disclosure. - `corpus_require_floor` greps `--test <name>` without checking which package it belongs to. - Lane worktrees are only indexed when `COSI_WORKTREES_DIR` is set (#238), and our own `.mcp.json` did not set it until now.
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.

Reference
h-dv/code-index#309
No description provided.