| name | capability-registry-harness |
| description | Use when a project's capability / asset / effect / component inventory is hand-maintained in a JSON (or YAML/TOML) manifest and drifting out of sync with the code that actually implements it โ built-but-unwired capabilities that the next workflow run can't see. Also use when someone wants a single-source-of-truth registry that auto-detects and auto-maintains a catalog (especially for agent-driven workflows that need a discoverable inventory). Triggers on "registry drifting", "manifest out of sync", "code-as-truth", "auto-generate the catalog", "stop hand-editing the registry", "make the JSON a build artifact", "harness for capabilities/assets/effects/layers". |
Capability Registry Harness
A reusable protocol for turning a hand-maintained capability manifest into a generated build artifact whose source of truth is the code itself, with drift caught as a CI failure. Project-agnostic โ instantiate it per product (video pipelines, asset pipelines, component libraries, tool catalogs).
Full architecture rationale (why not SQLite, the source survey, the war stories): references/architecture-recommendation.md (the original research brief from the first instantiation). This skill DISTILLS it into an applyable protocol โ read the brief only when you need the "why."
Copy-paste starters live in templates/ (parsers, schema canary, JSON-Schema export, drift-report skeleton, pre-commit hook, npm wiring) โ extracted from the two proven instantiations. Start a new project from those files, not from prose.
When to use / when not
Use when:
- A JSON/YAML/TOML manifest is a hand-maintained second copy of facts that already live in code, and they drift. The textbook symptom: code ships a capability (a new effect, material, layout, transition) but the manifest isn't updated, so the capability exists but is invisible to the next workflow run.
- There are many evolving capabilities, growing roughly linearly (every feature adds an entry). Drift cost grows with the count.
- An agent/LLM workflow reads the manifest to decide what it can do. A stale manifest silently narrows the agent's choices.
Do NOT use when:
- A handful of static entries that change a few times a year. A hand-edited list + code review is cheaper than a harness. Scope DOWN is the recurring failure mode (see caveats).
The core principle
Code is the source of truth. The manifest is a GENERATED build artifact. Drift is a CI failure.
One schema defines everything. You do not maintain a type AND a manifest AND a validator separately โ you derive all three from a single schema definition. Inverting authority (code โ manifest, not manifest โ code) is the actual fix; swapping the storage engine (JSON โ SQLite) without inverting authority just moves the same drift into a harder-to-diff container.
The bigger picture โ why this is a capability layer, not bookkeeping. Once the registry is
code-as-truth and drift-gated it serves two sides at once: Side A โ a machine contract an
agent BINDS to (every {ref} resolves or the gate is red), and Side B โ an inject-and-read
knowledge layer (a "simpler RAG": inject the catalog/digest, let the agent navigate with native file
tools โ no vector store until a measured low-hundreds bottleneck). A generated, drift-gated
knowledge index (genre/domain) can ride on top with the same dangling-reference gate, an
accretion loop grows it from demand, and outcome-driven governance (retirement + active-cap +
contribution score โ dedup alone is not coherence) keeps the growth from rotting. The marked
extension hook is cross-project federation (shared schema package + namespacing/versioning). Full
philosophy + the load order + the governance evidence: references/two-sided-registry-and-genre-layer.md.
The architecture
Four layers. Build them in order; each is independently useful.
-
One schema โ a discriminated union on kind (e.g. effect | material | layout | atom | โฆ). From this ONE definition derive: z.infer โ the TS types (replaces hand-kept unions); safeParse โ the build-time validator; z.toJSONSchema โ the machine-readable manifest the agent/MCP layer consumes. No second type, no second JSON to drift. Zod v4 is the recommended lib for a TS shop (best LLM-tool + JSON-Schema story); TypeBox if you want JSON-Schema-native.
-
Co-located self-describing sidecars + a TINY owned glob/generate script. Each capability declares itself next to its implementation (a typed registration object, a meta.json, an exported const). A small in-repo glob script discovers them. A capability cannot exist without being discovered โ this structurally eliminates the built-but-unwired bug. Never adopt a heavyweight codegen framework โ own a dumb in-repo glob+generate script (see caveats: codegen tools rot).
-
The manifest is a generated artifact. Regenerate by globbing sidecars and resolving references. It stays in git (diff-reviewable) but is never hand-edited. A header banner marks it generated.
-
CI lockstep gate โ fold into the existing validate command: (a) regenerate-then-diff โ rebuild from code, fail non-zero if it differs from committed; (b) dangling-reference check โ every composite reference resolves to a real lower-layer entry; (c) cross-source check โ every linked copy of the same capability agrees (code union โ code registry โ dispatcher map โ prompt files โ package scripts). Pre-commit hook for fast feedback; CI for the hard gate.
Three-tier layering (the "rigid system of layers"): primitive โ semantic โ composite. Map your catalog onto these and validate that every reference resolves:
- Primitive / reference โ raw capabilities: "what exists." Self-registering in the kit.
- Semantic / decision โ intent-named entries (e.g.
inspect/compare/warn): "what it means / when to use it." Agents pick from THIS layer.
- Composite / component โ named combos that reference semantic entries: "where it's used."
Add tiers lazily โ most leading systems stop at two until a third is forced. Don't pre-build a fourth.
The staged rollout protocol
This is the safe migration order. Do NOT jump straight to a hard gate โ you'll either block legitimate work or rubber-stamp a generator nobody trusts yet. The video pipeline's cut-1โcut-2 staging is the proof (see worked example).
- Report-only drift mode FIRST. Write the drift detector so it ALWAYS exits 0 โ a mirror, not a guard. Prove it catches the real drift you already know about before changing any authority. This is high-value on day one and zero-risk.
- Kit/primitive-first sidecars. Add self-describing sidecars to the actual drift source first (usually the lowest layer โ the kit/library that ships capabilities). Let the generator own that section.
- Flip authority to generated. Once sidecars cover a section, regenerate that section and stop hand-editing it.
- Hard CI gate + pre-commit. Once sidecars cover everything, flip the validate command to fail non-zero on drift. Add the pre-commit hook.
- Expose JSON-Schema as an agent/MCP resource. Emit JSON Schema from the same schema source so the workflow queries live capabilities instead of stale prose in a skill file.
Per-project instantiation checklist
Concrete steps to drop this harness into a NEW product:
- Define the schema from the current manifest shape. The existing manifest IS the spec โ modeling it is mechanical. Faithfully model the current shape first; do not "improve" it yet. The schema must
safeParse the current manifest cleanly. If it disagrees, fix the schema, never the manifest.
- Identify code-discovery selectors per capability kind. For each kind, name the exact code artifact that proves the capability exists: a TS union, a discriminated-union discriminant, an exported registry const's keys, a directory of
*Material.tsx files, a tree of meta.json files. Write down the selector and the id-derivation rule (e.g. GlassSweepMaterial.tsx โ glass-sweep).
- Mark which kinds are code-discoverable vs manifest-authored. Some kinds have no code source (pure decision-layer taxonomies like intents, audio-cue types, presenter layouts). These stay manifest-authored โ the harness skips them, by design. Be explicit about the skip list so a reviewer doesn't mistake it for missing coverage.
- Write the report-only drift script (see core principle + step 1). Run it. Confirm it surfaces the drift you already know about. This is the proof gate before changing authority.
- Then stage the flip per the rollout protocol.
A clean schema model + a code-discovery selector per kind + a skip list for manifest-authored kinds is the whole instantiation. Everything after is staging. The templates/ files give you steps 4โ5's scripts and the Stage-5 hook ready-made; only schema.ts and the generator are written per-project.
Anti-drift caveats (load-bearing โ these kill the project)
- Codegen tools rot; churning the generator IS the rot. Teams cycle through ORM-style generators, each trading one breakage for another. Own a tiny, dumb, in-repo glob+generate script. No heavyweight third-party generator, no magic. A bespoke generator matching your exact shape is more reliable than an off-the-shelf one precisely because the catalog is small and owned.
- Export EXPLICIT concrete registration objects. A generated type that is correct in the kit can degrade to a generic when consumed across a package boundary. Don't rely on inferred generics surviving kitโconsumer. Put the schema in one package both import; export concrete objects.
- Scope DOWN. Over-scoped catalogs demand constant maintenance and rot. Two tiers until a third is forced. Use a
$deprecated flag for staged removal; semver the schema.
- Name ONE owner for the schema + the validate command, or the gate becomes wallpaper that everyone
--no-verifys past.
- Determinism / render constraints are a per-capability QC concern the schema can FLAG but not fully validate. Example: in a Remotion + R3F pipeline, every motion value must derive from
useCurrentFrame() (never useFrame() / Date.now() / unseeded Math.random()), or parallel render workers flicker. The schema can carry a qc: [] field naming this constraint per capability; it cannot prove the code obeys it. Keep the deep determinism + QC discipline where it lives (the project's R3F rules + QC gate) and have the schema reference it, not duplicate it.
Worked example โ a video-production pipeline, cut 1 (current practice)
A Remotion video pipeline whose shared/capabilities/video-production-registry.json was a hand-maintained second copy. Cut 1 shipped the report-only foundation (NO authority flip yet):
| File | Role |
|---|
shared/capabilities/schema.ts | One Zod model of the registry. Discriminated unions on kind for effects[]/atoms[]; z.infer exports the future single-source-of-truth types. Faithfully models the current JSON โ not yet the authority. |
shared/capabilities/schema.check.mjs | npm run registry:schema-check โ safeParses the JSON against schema.ts. The canary that the schema still matches the JSON. Exits 1 on mismatch so it can gate later. |
pipeline/registry/drift-report.mjs | npm run registry:drift โ report-only codeโregistry membership drift. ALWAYS exits 0. |
pipeline/validate-capability-registry.mjs | The pre-existing validator (npm run validate:capabilities). |
What validate:capabilities already does vs what the drift detector adds. The existing validator checks within the manifest + exposed unions: unique ids, renderer/intent validity, combo references resolve, sourcePath exists, scene-atom meta.jsonโregistry ids, and that the hand-kept TS unions (CreatorEffectType, ThreeEffectId, TransitionSegment, EmphasisCue, AudioCue, GestureCue) are exposed in the registry. The drift detector adds the membership drift the validator misses โ the gap between what the CODE can do today and what the manifest advertises โ plus cross-source mismatches between linked copies of the same capability (kit union vs kit registry vs the consumer's dispatcher EFFECT_MAP; kit material files vs kit registry vs the consumer's kitMaterials).
Real drift it surfaced (proving the harness before flipping authority):
- KIND 1 ยท kit effects MISSING from registry:
connector-3d, headline-text-3d, surface-3d (+ cross-source: star-wrap in kit registry but not the EffectId union).
- KIND 2 ยท kit material MISSING:
glass-sweep (the textbook built-but-unwired case).
- KIND 3 ยท slide layouts MISSING:
formula-block, line-chart, numeric-matrix, status-list, terminal-card.
- KIND 4 ยท promo shot types + frames โ entire kinds unmodeled (no registry section yet).
- KIND 5 ยท a feature module's transition grammars MISSING:
blur-reveal, luma-wipe, match-cut, push-through.
KNOWN false-positive โ FIXED in STAGE 1. KIND 6's listMetaIds globbed only scenes/*/meta.json โ one level deep โ and treated any registry atom without a one-level meta.json as dangling. STAGE 1 made the meta discovery recursive AND added a rendered-media cross-check: a registry atom is backed-by-code if it has a meta.json at any depth OR rendered media (<id>.mp4|webm|mov). This cleared all five spurious danglings (a namespaced nested scene atom nested-scene-a, a second nested-scene-b, and three meta-less transition atoms transition-zoom-in, transition-pan-left, transition-pan-right). The rendered-media check is what actually resolves them: the nested scene atoms carry the namespaced id only on their rendered file (their meta.json id is the bare unqualified name), and the transition atoms ship as rendered media with no meta.json at all. (Side effect, by design: the recursive glob now surfaces the bare nested meta ids as (a) missing from registry. That is a real membership/namespacing observation, not a spurious dangling; one of them is a genuine built-but-unwired nested atom โ call it nested-orphan.)
STAGE 1 โ first authority flip (kit/primitive-first). The first kind flipped from report-only to generated+gated was KIND 2 (kit materials), per the "flip code-discoverable kinds first" lean. Files added: pipeline/registry/build-registry.mjs (tiny owned generator, stdlib-only ESM) owning ONLY slideLayouts.kitMaterials; npm scripts registry:build (regenerate-in-place) and registry:check (regenerate-then-diff, exit 1 on drift โ the hard gate for this section only). Source of truth: the 3D kit's registry/three-effects.json materials[]. The generator does a surgical in-place rewrite of just the "kitMaterials": [ โฆ ] block (regex-matched), preserving every other byte and the file's compact one-object-per-line style; ordering is stable (existing ids first, new kit materials appended); hand-added brush tags are carried forward by id. The flip added glass-sweep (the textbook built-but-unwired case) and left KIND 2 reporting in sync. registry:drift stays the broad report-only mirror (exit 0) for kinds 1, 3, 4, 5, 6; registry:check is NOT yet wired into pre-commit/CI (that is STAGE 5). Determinism caveat held: only kitMaterials owned; effects[]/layouts[]/promo/transitions/atoms untouched.
STAGE 2 โ membership-gate (not generate) a code union. KIND 1 (kit effects) flipped, but NOT by generating effects[] โ the effects entries carry rich hand-authored prose (useWhen/avoidWhen/qc) that is not derivable from code. Instead registry:check gained Gate B: every kit EffectId union member (parsed from the kit's src/types.ts) must be ACCOUNTED FOR โ either EXPOSED as a three-effect effects[] entry, or deliberately listed in a new top-level kitInternalEffects array (recorded in the catalog, self-documenting, not hidden in code). A member that is neither fails the gate. This is the key generalization: generate only pure-derivable data (kitMaterials = {id, source}); membership-gate kinds that carry human prose. The flip exposed headline-text-3d and recorded connector-3d+surface-3d as kitInternalEffects; validate:capabilities and the drift report both honor that exclusion list.
STAGE 3 โ fold a second product (promo) into the ONE shared catalog. The registry was a lesson-shaped manifest; promo lived as an island (its own PromoPlan schema, its own validate-promo-plan.mjs). Stage 3 brought promo's three code-truth kinds IN as hard-gated sections โ promoShotTypes (the Shot union discriminant), promoFrames (PROMO_FRAME_REGISTRY keys), transitionGrammars (SlideTransitionType) โ each membership-gated by Gates C/D/E (exact set equality, codeโregistry; no generation, prose hand-authored from the source comments + components). Two design moves worth reusing: (1) a composition-role dimension as metadata, not a structural rewrite. Each entry carries a compositionRole tag (base-track/b-roll/overlay) and a new compositionRoles descriptor records the layer-stack taxonomy + the product's recorded-mode invariant ONCE. "One shared catalog" was satisfied by one file + a shared role taxonomy โ NOT by collapsing the per-section arrays into one flat catalog[] (that rewrites the schema + every section-keyed validator/consumer; rejected as over-scoped, consistent with the scope-DOWN caveat). (2) the drift report caught a second-order drift source: validate-promo-plan.mjs kept its OWN hand-maintained supportedShotTypes/knownFrameIds Sets โ duplicate copies of the same code unions โ so KIND 4 gained a report-only cross-source check against those Sets. Also extracted the shared TS-source parsers into pipeline/registry/code-unions.mjs (both drift-report.mjs and build-registry.mjs import it) so the two scripts can't drift in HOW they read code โ the parser itself was becoming a duplicated-second-copy.
STAGE 4 โ cheap flip once the mirror is broad. Flipped KIND 3 (slide layouts): 5 layouts (numeric-matrix/formula-block/status-list/terminal-card/line-chart) were in the SlideLayout union but missing from slideLayouts.layouts[]. Membership-gated (Gate F, exact set equality) โ prose-bearing, so authored not generated, like Stages 2โ3. The reusable lesson: this flip needed only TWO edits โ author the JSON entries + add the gate. NO schema change (the existing slideLayoutEntrySchema already modeled the entry shape) and NO drift-report change (KIND 3 was already wired to diff(SlideLayout-union, slideLayouts.layouts ids) from cut 1 โ it auto-flipped to in-sync the moment the entries existed). Once the report-only mirror covers a kind, flipping it to gated is cheap: fill the section + promote to a hard gate. Two honest authoring notes carried from the build: (1) layout caps in the catalog are a READABLE re-encoding of the preflight validator's caps (eyebrowMax:60 โ "eyebrow": 60; min+max count pairs โ a "1..6" string) โ the validator stays the authoritative source of the numbers, the catalog mirrors them in its house style; (2) numeric-matrix/line-chart are legitimately dual-purpose (a standalone layout AND a SlideFigure figure-kind) โ same id in two sections is correct, not a dedupe target.
STAGE 5 โ the finish line (hard gate + agent-discovery export + last cleanup). Three independent moves closed the harness: (1) Versioned pre-commit gate. A committed .githooks/pre-commit runs registry:check + registry:schema-check; a prepare npm script (git config core.hooksPath .githooks || true) self-installs it on npm install. Husky was rejected (a dependency โ against scope-down); a plain .git/hooks script was rejected (not versioned โ the skill's own "becomes wallpaper" warning). tsc/validate:capabilities stay OUT of the hook (too slow for the commit path). The very commit that added the hook self-tested it. (2) JSON-Schema export for agent discovery. pipeline/registry/export-schema.mjs emits z.toJSONSchema(registrySchema) โ video-production-registry.schema.json (banner-first, JSON.stringify(โฆ,2)), regenerated by registry:build and regenerate-then-diff gated by registry:check. Deliberate trade-off recorded: gating a THIRD-PARTY generator's output (zod's toJSONSchema) couples the gate to zod's output stability โ accepted because the failure mode is benign and self-explaining ("run registry:schema-export"), and consistency with the harness's regenerate-then-diff principle won. A live MCP-resource server was deferred (emerging, heavier; the static schema file is the 80/20). (3) Last built-but-unwired cleanup. Dropped the one genuinely dead nested atom (nested-orphan โ complete source, rendered never, referenced nowhere), and taught KIND 6 to resolve on-disk atoms against the registry's sourcePath (directory match) instead of by id โ so namespaced-but-wired atoms (a bare meta id โ its namespaced registry id) stop false-flagging. Net end state: 7 kinds, 6 in sync, 1 benign known cross-source note (star-wrap); the full code-truth surface (Gates AโF) is hard-gated + pre-commit-enforced, and the schema is exported for agents. The harness is complete.
Detail per-kind selectors, the comment-stripping TS-union parser, and the id-derivation rules live in references/instantiation-cookbook.md so the worked example here stays a summary.
Worked example 2 โ an SVG component library (second instantiation, complete)
An SVG lesson-animation component-library repo, instantiated end-to-end (all 5 rollout stages) as src/capabilities/schema.ts + scripts/registry/*.mjs. What it ADDED to the protocol beyond the video pipeline:
- Barrel discovery + generate-with-prose-carry-forward. Its "what exists" source is component BARRELS (
parseBarrelValueExports, now in the shared parsers). build-registry.mjs regenerates the STRUCTURAL fields (kind/component/source, motion vocabulary) from the barrels and carries the hand-authored prose (intent/useWhen/avoidWhen/variants) forward by id โ a hybrid of the generate-vs-gate split: existence + structure generated, prose preserved, 0 entries still need prose reported. Hand-editing prose then rebuilding is a no-op (idempotent).
- Stranded-export gate. A PascalCase component exported from the barrel via an UNREGISTERED family module would be silently uncatalogued by the generator โ so it's hard drift, not a skip. (Membership-completeness: account for every code member or fail.)
- Self-completing preview surface.
check-gallery.mjs fails registry:check if any registered component lacks a studio gallery demo (demoProps) โ the human review surface structurally cannot lag the catalog.
- More derived read-views, all regenerate-then-diff gated:
catalog-digest.md (planner-readable digest the agent workflow consumes), recency.json, the JSON-Schema export, a generated icon-asset catalog (90 traced SVGs), and an auto-built lesson registry (scenes register by being built, composer stops editing Root.tsx).
- End state: ONE
registry:check chaining 6 gates (icons / catalog / digest / schema-export / gallery / lessons), Stage-5 .githooks/pre-commit + prepare self-install, drift-report 7/7 kinds in sync. Sound assets are an explicit skip-list entry (owned by the lesson pipeline's sound lane, not the registry).
Optimization log & open questions
Append-only. A future session can open this skill and either apply the protocol to a new product OR optimize the protocol itself. Date each entry. Be honest about proven vs aspirational.
- [resolved ยท 2026-05-31] Nested-atom glob. KIND 6 in
drift-report.mjs globbed one level deep, producing false "dangling" reports for nested scene atoms and meta-less transition atoms. STAGE 1 fixed it: meta discovery is now recursive, plus a shared/atoms/ rendered-media cross-check marks an atom backed-by-code if it has rendered output even without a meta.json (or with a namespace-mismatched meta id). All five spurious danglings cleared. KIND 6 is now clean enough to promote to a hard error in a later stage; the remaining KIND 6 (a) missing entries are real namespacing/unwired observations, not false alarms.
- [deferred] Embeddings / semantic agent selection. For a catalog of dozensโlow-hundreds, a flat searchable manifest + good
description/intent fields is sufficient. Add a vector index ONLY as a derived read-cache if agent selection becomes a measured bottleneck โ never as the authoring surface. No benchmark currently justifies it.
- [extension point ยท NOT built] Cross-project federation. The current harness is per-product. A future option: factor the Zod schema into a shared schema package that multiple products import, with namespacing + versioning so per-product registries can integrate/compose across products (mirrors how a multi-product repo already shares its kit packages). This is a marked extension hook, not implemented. Decide it only when a second product actually needs to consume another's catalog. Federation framing + the two-sided contract/knowledge extension:
references/two-sided-registry-and-genre-layer.md ยง8.
- [proven] TS runner. Node native type-stripping (Node โฅ 22.6, default-on โฅ 23) loads
schema.ts from a plain .mjs with zero build step โ no tsx/ts-node. Keep the schema plain Zod (no enums / namespaces / decorators) so it strips cleanly. Silence the experimental warning via the npm script.
- [resolved ยท 2026-05-31] When to flip authority. Per-kind flips, value lands incrementally โ confirmed across Stages 1โ3. Order that worked: pure-derivable kit data first (Stage 1 kitMaterials, generated), then code-union kinds that carry prose (Stage 2 effects, Stage 3 promo shots/frames/transitions โ membership-gated, NOT generated). The generate-vs-gate split is the load-bearing rule: generate only when the entry is pure derivable data; membership-gate when it carries hand-authored prose (existence stays code-truth either way). Do NOT wait to flip a kind until every kind has a section.
- [resolved ยท 2026-05-31] Per-section vs one flat catalog. When folding a second product/consumer into the registry (Stage 3 promo), keep the existing per-section arrays and add the cross-cutting dimension as a per-entry tag + one descriptor block โ do NOT collapse to a single . A flat array rewrites the schema and every section-keyed validator/consumer for a conventional (not structural) gain; it also flattens genuinely different entry shapes. "One shared catalog" = one file + a shared taxonomy, not one array. (Revisit only if a planner demonstrably needs to enumerate ALL capabilities without knowing section names โ then add a derived flat , not a flat authoring surface.)
Changelog
- 2026-06-13 โ Added
references/two-sided-registry-and-genre-layer.md: the registry as a capability layer with TWO sides (Side A = drift-gated machine contract you bind to; Side B = inject-and-read "simpler RAG" knowledge layer, defer embeddings until a measured low-hundreds bottleneck); a generated drift-gated knowledge index (genre/domain) on top with the same dangling-reference gate; the accretion loop; and outcome-driven library governance (retirement + bounded active-cap + authoring prior โ dedup alone is not coherence; SkillsBench/Library Drift, arXiv 2605.19576). Cross-linked the federation extension hook to that doc. Pointer added under "The core principle"; all existing worked-examples and the optimization log untouched (additive). Authored the publishable README.md and put the repo under public git.
- 2026-06-12 โ Made the skill directly adoptable: added
templates/ (portable code-unions.mjs, schema-check + export-schema with ADAPT markers, drift-report skeleton, pre-commit hook, npm wiring + the two laws). Added worked example 2 (the SVG component library's 6-gate instantiation: barrel discovery, prose carry-forward, stranded-export gate, gallery completeness gate, derived read-views). Logged + fixed the one-module-per-shared-fact lesson (families.mjs โ the mirror's stale private MODULE_KIND copy false-flagged IconAsset). Canonical home put under git.
- 2026-05-31 โ Initial authoring (cut 2a). Distilled from the architecture brief + the video pipeline's cut-1 implementation. References: architecture brief copy + instantiation cookbook.
- 2026-05-31 โ STAGE 1 in the video pipeline: first authority flip (kit materials โ generated via
pipeline/registry/build-registry.mjs + registry:build/registry:check), and the KIND 6 nested-atom-glob false-positive fixed (recursive meta glob + rendered-media cross-check). Worked example + optimization log updated; the open nested-atom-glob fix is now resolved.
- 2026-05-31 โ STAGE 2 in the video pipeline: membership-gate (not generate) for a prose-bearing code union โ Gate B hard-gates kit
EffectId membership (exposed in effects[] OR listed in kitInternalEffects). Established the generate-vs-gate split.
- 2026-05-31 โ STAGE 5 in the video pipeline: harness COMPLETE. Versioned pre-commit gate (
.githooks/pre-commit + prepareโcore.hooksPath; husky/local-hook rejected), JSON-Schema export (export-schema.mjs โ z.toJSONSchema artifact, regenerate-then-diff gated; live MCP server deferred), and the last KIND-6 cleanup (dropped the one dead nested atom; drift-report now resolves on-disk atoms by registry so namespaced-but-wired atoms don't false-flag). End state: Gates AโF hard-gated + pre-commit-enforced, schema exported; 6/7 kinds in sync, 1 benign note. Worked example + changelog updated; rollout-protocol steps 4โ5 now realized.