Skip to content

feat(store): M1 invoke slice — pure-Zig voc store with honest gate (ADR-0002) - #13

Merged
hyperpolymath merged 2 commits into
mainfrom
feat/voc-m1-invoke-slice
Jul 28, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
feat/voc-m1-invoke-slice

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

What

The first real vocarium implementation, per the approved 2026-07-28 plan and owner rulings: pure Zig, wire boundary, no C.

  • voc (src/store/): append-only JSONL store replayed into in-memory indexes. voc ingest validates (the warrant door: any edge citing a missing/unknown warrant is refused at exit 2 — EXPLAINME's falsifiability item, 'unwarranted substitutions'). voc invoke --use-model <id> <trope> emits a Trope IR v0.2 Document — the wire contract; grades/floors pass through opaque because the grading algebra belongs to trope-checker, never here.
  • Honest gate (tests/e2e/invoke_slice.sh, just gate, vocarium-ci.yml): tool-pin hard-asserts before any test (zig exact-version vs .tool-versions; tropecheck-rs built from pinned SHA 69221ad6), then unit tests, canonical ingest, and the use-relativity proof: the same stored trope (README's 'authentic language' → 'real language', quality Attenuated(6)) must be p-sufficient (exit 0) under casual-note and p-insufficient (exit 1, witness=e_paraphrase coord=fate.quality) under critical-paraphrase. Neither an empty store nor a stubbed path can pass both. Negative case: unwarranted edge → exit 2.
  • Scaffold cure (ADR-0002): deletes the C-ABI shim template (src/interface/, abi.ipkg), template test machinery, validate-template.sh, docs-template/. CLAUDE.md identity corrected (was claiming rsr-template-repo). Justfile build/test/e2e were echo-pass fakes — now real zig commands.
  • ADR-0002 (docs/decisions/): the ruling and its consequences, including the only sanctioned future linkage path (Zig export fn, still zero C source).

Verified locally (evidence, not claims)

  • just gate → GATE PASS (full path incl. clone+build of tropecheck at pin).
  • Falsifier 1: TROPECHECK_BIN=/nonexistent → GATE FAIL: tropecheck binary not executable (exit 1).
  • Falsifier 2: .tool-versions pin altered → GATE FAIL: zig version skew (exit 1).
  • zig fmt --check clean; 6/6 unit tests.

Follow-ups (out of M1 scope, tracked in plan)

  • M2: Rust adapter implementing hermeneia_store::StoreProvider by driving voc.
  • ~40 files still carry {{PROJECT*}} template placeholders (docs/config, not code) — estate RSR-cure scope.
  • .machine_readable/ identity (uuid/clade) still template-minted; needs cure + just claude-md regeneration.

🤖 Generated with Claude Code

…DR-0002)

The first real vocarium implementation. Owner ruling 2026-07-28: pure Zig,
wire boundary, NO C — the store's only contract is Trope IR v0.2 JSON over
process I/O; tropecheck is the verifier.

- src/store/: the voc executable. Append-only JSONL replay into in-memory
  indexes; warrant-door validation (an edge citing no/unknown warrant is
  refused at exit 2 — EXPLAINME falsifiability item 7); voc invoke emits the
  IR Document for the path into a trope under a named use-model. Grades and
  floors are carried opaque — vocarium never interprets the grading algebra.
- tests/e2e/invoke_slice.sh: the honest gate. Tool-pin hard asserts BEFORE
  tests (zig version vs .tool-versions; tropecheck built from pinned SHA
  69221ad6); then zig build test, canonical ingest, the use-relativity pair
  (casual-note -> p-sufficient exit 0 AND critical-paraphrase ->
  p-insufficient exit 1 with witness=e_paraphrase), and the unwarranted-edge
  refusal. Falsifiers verified: missing tropecheck and zig-pin skew both
  fail the gate loudly.
- .github/workflows/vocarium-ci.yml: the gate in CI; self-contained steps,
  SHA-pinned actions (repo has sha_pinning_required).
- Scaffold cure per ADR-0002: deleted src/interface/ (C-ABI shim template,
  wrong shape by ruling), abi.ipkg, template test machinery,
  validate-template.sh, docs-template/; CLAUDE.md identity corrected from
  rsr-template-repo to vocarium + architecture section; Justfile
  build/test/e2e/gate now run real zig commands (were echo-pass fakes);
  README gains an honest Status section.
- examples/vocarium-ingest.jsonl: the README's worked example as the
  canonical fixture (authentic language -> real language, Attenuated(6)).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: CC-BY-SA-4.0
Comment thread src/store/store.zig
Comment thread .github/workflows/vocarium-ci.yml
@hyperpolymath
hyperpolymath marked this pull request as ready for review July 28, 2026 14:48
@hyperpolymath
hyperpolymath merged commit 2efab1f into main Jul 28, 2026
32 of 38 checks passed
@hyperpolymath
hyperpolymath deleted the feat/voc-m1-invoke-slice branch July 28, 2026 14:49
@sonarqubecloud

Copy link
Copy Markdown

@gitar-bot

gitar-bot Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Note

Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime.
Learn more

Code Review ✅ Approved 2 resolved / 2 findings

Implements the M1 invoke slice with a pure-Zig JSONL voc store and honest gate according to ADR-0002, but introduces a critical use-after-free bug in store aliases and a minor hardcoded Zig version issue in CI.

✅ 2 resolved
✅ Quality: CI hardcodes Zig version instead of reading .tool-versions

📄 .github/workflows/vocarium-ci.yml:24-28 📄 tests/e2e/invoke_slice.sh:19 📄 tests/e2e/invoke_slice.sh:27
vocarium-ci.yml pins version: 0.16.0 literally in the setup-zig step, but the gate script (invoke_slice.sh:19,27) asserts against .tool-versions. The two pins can drift: if .tool-versions is bumped, CI would install the old version and the gate's own skew check would fail (or vice-versa). Since the gate script already hard-asserts the exact version, drive setup-zig from .tool-versions (mlugg/setup-zig can read it) or add a comment tying the two together so they're bumped in lockstep.

✅ Bug: Store aliases freed file buffer (use-after-free)

📄 src/store/store.zig:125 📄 src/store/store.zig:136-142 📄 src/store/main.zig:64-71
In store.zig:125 the JSONL is parsed with std.json.parseFromSliceLeaky(..., .{}), whose default allocate = .alloc_if_needed makes unescaped string values (trope ids, quality/bearer/context/record, edge fields, use-model/warrant ids) alias the input line/bytes slice rather than copy into the arena. In main.zig:64-71 loadStore() does defer gpa.free(bytes) and returns the Store, so bytes is freed the moment loadStore returns while the caller (runIngest/runInvoke → pathInto/emitDocument) keeps using those slices — a use-after-free. The unit tests don't catch it because their fixtures are static string literals that never get freed; the CLI path is the one that frees. This is latent UB that may be masked by allocator behavior today but can corrupt output or crash. Fix by forcing the parser to own its strings (or by keeping bytes alive for the Store's lifetime).

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

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.

2 participants