fix(abi): restore honest Foreign.idr — every %foreign resolves to a real export#95
Conversation
…nks (gossamer#95) (#96) ## Summary Resolves the root cause behind **gossamer#95** ("restore: honest ABI decoupling from groove") — fundamentally and at source, rather than as a merge-conflict patch. Two structural defects made the ABI dishonest and fragile; both are fixed and then **gated so they cannot silently recur**. Everything is verified with the real toolchain (`idris2 0.8.0`, Chez Scheme, built to match `abi-typecheck.yml`): both packages `--typecheck` green and the 147-test ABI suite passes. This is an alternative, clean-branch resolution of the same intent as #95 (which is currently `dirty`/conflicted against `main`). Unlike #95 it **deletes no proofs** — every module is preserved, one is added. ### Root cause 1 — duplication (the "honest ABI" part) `src/interface/Gossamer/ABI/<M>.idr` had been committed as **byte-identical regular-file copies** of the canonical `src/interface/abi/<M>.idr`. The `120000` symlinks introduced in `#42` were materialised into files at some point, so every ABI module lived **twice** — the two trees could drift while `gossamer-abi.ipkg` still claimed *"symlinks map the namespace to the physical files"*. **Fix:** restored all 15 namespace entries to `120000` symlinks into the canonical `abi/` tree. One physical copy per module; nothing to drift. ### Root cause 2 — conflation (the "decoupling from groove" part) `gossamer-abi.ipkg` bundled the Groove inter-service protocol proofs into the webview **shell**, and shell modules imported `Gossamer.ABI.Groove`. - The imports in `ResourceCleanup` and `IPCDispatch` were **vestigial** — their `TGroove`/`CmdGroove*` only use shell types (`HandleToken` is defined in `HandleLinearity`, not Groove). Removed. - `HandleLinearity`'s **genuine** groove use (`grooveValid`, `LinearGroove`, `allocateGroove`, `consumeForDisconnect`) was **extracted** into a new groove-layer module `Gossamer.ABI.GrooveLinearity`, which instantiates the shell's *generic* `LinearHandle` for `GrooveHandle`. The shell now type-checks **without Idris2 ever compiling `Groove`**. Layering points one way only: Groove depends on the shell, never the reverse. ## Changes - **`gossamer-abi.ipkg`** — groove-agnostic shell (11 modules): `Types`, `Layout`, `Foreign`, `IPCDispatch`, `HandleLinearity`, `WindowStateMachine`, `LayoutStability`, `IPCIntegrity`, `PanelIsolation`, `ResourceCleanup`, `AndroidComponents`. - **`gossamer-groove.ipkg`** (new) — Groove protocol proofs (4 modules): `Groove`, `GrooveLinearity`, `CapabilityAuthenticity`, `GrooveTermination`; `depends = base, gossamer-abi`. - **`src/interface/abi/GrooveLinearity.idr`** (new) — the extracted groove-handle linearity; zero `believe_me`, `%default total`. - **`src/interface/abi/{HandleLinearity,IPCDispatch,ResourceCleanup}.idr`** — decoupled from `Gossamer.ABI.Groove`. - **`src/interface/Gossamer/ABI/*.idr`** — restored to symlinks (`100644` → `120000`). - **`scripts/check-abi-decoupling.sh`** (new) — fast, toolchain-free structural guard: fails if a namespace file is a materialised copy, if a shell module re-imports a groove module, or if the two `.ipkg`s stop partitioning the modules. Shellcheck-clean; proven to fail on both regressions and pass on the fixed tree. - **`.github/workflows/abi-typecheck.yml`** — new `abi-structure` guard job (seconds, gates before the ~15-min build) + typecheck **both** packages. - **`Justfile`** `abi-check` — runs the guard and both typechecks. - **Docs** — `README.md`, `PROOF-NEEDS.md`, `GETTING-STARTED.md`, `CHANGELOG.md`. ## RSR Quality Checklist ### Required - [x] Tests pass (`gossamer-abi-tests`: **147 passed, 0 failed**, idris2 0.8.0) - [x] Code is formatted (matches house style; `scripts/*.sh` shellcheck-clean) - [x] Linter is clean (no new warnings; only pre-existing benign `IPCIntegrity` shadowing notes) - [x] No banned language patterns (Idris2 / Bash / YAML / Markdown only) - [x] No `unsafe` blocks without `// SAFETY:` comments (none added) - [x] No banned functions (`believe_me`, `unsafeCoerce`, `Obj.magic`, `Admitted`, `sorry`) — new module has none; the one sanctioned class-J axiom in `PanelIsolation` is untouched - [x] SPDX license headers present on all new/modified source files - [x] No secrets, credentials, or `.env` files included ### As Applicable - [x] Documentation updated for user-facing changes - [x] `CHANGELOG` updated - [x] ABI/FFI changes validated (`src/interface/abi/` self-consistent; `src/interface/ffi/` untouched) - [ ] `.machine_readable/STATE.a2ml` (no state change required) ## Testing Idris2 v0.8.0 built from source with Chez Scheme (identical to `abi-typecheck.yml`). Clean-room run of every gate: ``` GATE 1/5: structural decoupling guard ......... PASSED GATE 2/5: typecheck shell (gossamer-abi.ipkg) . GREEN (11 modules) GATE 3/5: install shell ....................... installed GATE 4/5: typecheck groove (gossamer-groove) .. GREEN (4 modules) GATE 5/5: build + run ABI test suite .......... TOTAL: 147 passed, 0 failed ``` Guard negative-tested in both directions: injecting `import Gossamer.ABI.Groove` into a shell module fails Check 2; an un-staged symlink (mode `100644`) fails Check 1; the correct tree passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013Zg1kEsjSRpcf1wFBZyeNj --- _Generated by [Claude Code](https://claude.ai/code/session_013Zg1kEsjSRpcf1wFBZyeNj)_ Co-authored-by: Claude <noreply@anthropic.com>
…eal export Rebases PR #95's honest-Foreign.idr fix (was 070010f) onto current main. Replaces the placeholder %foreign surface — 8 of the 11 declared symbols had no Zig definition (gossamer_init/free/process/get_string/process_array/ register_callback/is_initialized), the "phantom template" flagged as the CRITICAL item of gossamer#82 — with the real exported C-ABI symbols. All 25 C:libgossamer %foreign declarations now resolve to an actual `export fn` in src/interface/ffi/src/*.zig (verified 25/25 against main's Zig). The remainder of PR #95 (ABI de-conflation, standards CI adoption, governance docs, .machine_readable reorg, licensing) is superseded by work already merged to main — #96 (de-conflation + split packages), #87/#97 (MPL-2.0 licensing), #93 (reusable workflows), #78 (wiki) — and merging it as-is regresses those. This reduces #95 to its one non-superseded contribution so it merges without conflict or regression. Verified with idris2 0.8.0: gossamer-abi + gossamer-groove --typecheck green, 147/147 ABI tests pass, ABI decoupling guard passes. Addresses the CRITICAL item of gossamer#82. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Zg1kEsjSRpcf1wFBZyeNj
c63c6c5 to
fe56303
Compare
|
Unstuck this by rebasing the branch onto current Why not a straight merge: the bulk of the original scope (ABI de-conflation, standards CI adoption, governance docs, So I kept the one piece The original 13 commits are recoverable from the force-push history if you want to cherry-pick anything else out of the old scope. This also closes the CRITICAL item of #82. Generated by Claude Code |
…amer#82) (#98) ## Summary First concrete step on **#82's CRITICAL item** (the "phantom template"). `#95` fixed the honest `Foreign.idr`; this makes that fix **permanent and measurable**. The root problem #82 names: `idris2 --typecheck` proves the ABI internally sound but **cannot see whether a `%foreign "C:sym, libgossamer"` resolves to a real Zig export** — that linkage is only exercised at link time. That blind spot is exactly how 8 phantom symbols (`gossamer_init`/`process`/…) hid behind a green typecheck. This PR adds the missing check as a fast, toolchain-free gate. ## What the gate does — `scripts/check-abi-ffi-cleave.sh` - **HARD FAIL** on any ABI `%foreign` with no matching `export fn` in `src/interface/ffi/src/*.zig` — the ABI can never again lie about the FFI. - **REPORT** coverage of the real C surface: **29 / 130** `gossamer_*` exports are declared today; the **101 uncovered** are the tracked expansion work. Coverage is *reported, not failed*, so adding a new Zig export doesn't break CI before its ABI declaration lands. Wired into the `abi-structure` job (ahead of the ~15-min Idris2 build) and `just abi-check`. ## The ground truth it establishes (for #82) | | count | |---|---| | ABI `%foreign` C:libgossamer declarations | 29 | | Real Zig `gossamer_*` C exports | 130 | | **Phantom (declared, not exported)** | **0** ✅ | | Covered (declared **and** exported) | 29 | | **Uncovered (exported, not declared)** | **101** — expansion work | So #82-CRITICAL splits cleanly: *"no phantom symbols"* is now **done and gated**; *"the ABI describes the full real surface"* is **22%**, and is the next decision (see below). ## RSR Quality Checklist ### Required - [x] Tests pass (idris2 flow unchanged: both packages typecheck, **147/147**) - [x] Code formatted; `scripts/*.sh` **shellcheck-clean** - [x] Linter clean (no new warnings) - [x] No banned language patterns (Bash / YAML / Markdown) - [x] No `unsafe` without `// SAFETY:` (none) - [x] No banned functions (`believe_me`, `sorry`, …) — none added - [x] SPDX header on the new script - [x] No secrets ### As Applicable - [x] Documentation updated (`PROOF-NEEDS.md`, `CHANGELOG.md`) - [x] ABI/FFI change validated (the gate *is* the validation; run locally + negative-tested) ## Testing ``` cleave gate on current tree ......... PASS (0 phantom; 29/130 reported) cleave gate w/ injected phantom ..... FAIL (phantom listed) ✓ negative test shellcheck .......................... clean decoupling guard .................... PASS idris2 typecheck (shell+groove) ..... GREEN ABI test suite ...................... 147 / 147 ``` ## Next decision for #82 (not in this PR) Closing the **coverage** half (declaring the 101 uncovered exports) raises #82's open architectural question — *single source of truth + codegen*. My recommendation: **generate the `%foreign` block from the Zig `export fn` signatures** (Zig FFI = SoT), so the ABI can never drift from the FFI by construction, and this gate flips from "reports 22%" to "enforces 100%". Happy to build that next if you agree on the direction — or hand-declare incrementally by module if you'd rather keep it manual. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013Zg1kEsjSRpcf1wFBZyeNj --- _Generated by [Claude Code](https://claude.ai/code/session_013Zg1kEsjSRpcf1wFBZyeNj)_ Co-authored-by: Claude <noreply@anthropic.com>
Summary
Restores honest
%foreignresolution insrc/interface/abi/Foreign.idr: replaces the placeholder ABI surface — where 8 of 11 declared%foreignsymbols (gossamer_init/free/process/get_string/process_array/register_callback/is_initialized) had no Zig definition (the "phantom template" that is the CRITICAL item of #82) — with the real exported C-ABI symbols. All 25C:libgossamer%foreigndeclarations now resolve to an actualexport fninsrc/interface/ffi/src/*.zig(verified 25/25 againstmain).Scope change (rebased onto current
main)This PR was originally a ~292-file sweep (ABI de-conflation + standards CI adoption + governance docs +
.machine_readablereorg + licensing). Since it was opened,mainmerged equivalent-or-newer work that supersedes almost all of it:Merging the original sweep now would regress that merged work (e.g. it reverts the copyright-header standardization and re-introduces older workflow/licensing patterns), and produced 54 merge conflicts. So the branch has been rebased onto
mainand reduced to its one non-superseded contribution: the honestForeign.idr. The original 13 commits remain in this PR's force-push history if any other piece is wanted later.Verification (idris2 0.8.0, Chez — matches CI)
gossamer-abi.ipkg+gossamer-groove.ipkg--typecheckgreen%foreignsymbol mapping: 25 / 25 resolve to a real Zigexport fnAddresses the CRITICAL item of #82.