Skip to content

Write canonical Intuition vocabulary + predicate registry candidacy - #18

Open
jeremie-olivier wants to merge 29 commits into
mainfrom
feat/canonical-vocabulary
Open

Write canonical Intuition vocabulary + predicate registry candidacy#18
jeremie-olivier wants to merge 29 commits into
mainfrom
feat/canonical-vocabulary

Conversation

@jeremie-olivier

Copy link
Copy Markdown
Member

Stacks on #16. This branch is built on that PR's head, so the diff currently
includes its 23 commits. Review 647fa99
the single commit here. Once #16 lands, this diff reduces to that commit automatically.

The problem

Claims created in Ontology could not join the shared graph.

The app minted predicate atoms by hashing a bare label:

createAtomFromString(config, 'created by')          // hashes "created by"

The canonical registry hashes a JSON-LD DefinedTerm document:

calculateAtomId('{"@context":"https://schema.org/","@type":"DefinedTerm","name":"created by",…}')

Different bytes → different atom ids. Every predicate atom this app wrote was invisible to
the rest of the ecosystem, and claims from other Intuition apps were invisible here.

The canonical derivation did not exist when Mission 01 (#8) was built. This is drift,
not a defect in that work.

What changed

Canonical identity

  • src/lib/intuition/canonical.ts is the single boundary to the alpha @0xintuition/*
    packages — an alpha bump has one blast radius.
  • Predicate atoms carry canonical DefinedTerm bytes.
  • Writes preflight the deterministic id against the indexer and reuse the existing atom
    rather than minting a duplicate. Free, synchronous, no wallet required.
  • The minted id is asserted against the precomputed one. A mismatch throws rather than
    silently staking against the wrong atom.

Vocabulary reconciliation

Direct Direction-flipped Candidates / app-local
Predicates (56) 29 7 20
Entity types (39) 32 4 renamed 3

Directions were verified against each canonical predicate's description, not inferred
from its name — founder reads organization→person, so this app's founder of maps to
founded, not founder.

The 7 flips implement mint-time canonicalisation locally. ⟨A, employs, B⟩ and
⟨B, employedBy, A⟩ are one fact hashing to two triple ids and therefore two vaults,
splitting stake. Upstream audit A1 calls this "economically unsound"; their fix (item A8)
has not shipped. The claim builder discloses the swap before you stake.

Predicate registry

<candidate> — listed in → <predicate registry>

Ranked by net stake, contestable via counter-triples. Mints the registry atom when absent —
it is unminted on both mainnet and testnet today.

Candidacy is deliberately a different edge from ratified membership, which upstream
records as ⟨registry, contain, X⟩. A proposal is not yet true, so it must not be written
as though it were.

Read-only mode (bug fix)

WalletProvider previously rendered only a warning banner when VITE_PRIVY_APP_ID was
unset — replacing the entire application. An unconfigured optional integration took down
everything that never needed it. This is what the preview deploy was showing.

The read stack now sits outside the wallet stack, and wallet hooks select an inert
implementation at module load from the build-time constant.

Bundle

Before After
Entry chunk 3,221 kB 186 kB
Chunks 239 67
Read-only total 858 kB (Privy + wagmi tree-shake out)

Vendor splitting plus route-level lazy loading.

Docs

  • README.md replaced — it was still the Vite template.
  • .env.example documents that VITE_* are build-time only, including the Coolify
    build-variable requirement that caused the preview to ship an env-less bundle.
  • plan.md records the full analysis and open questions for the Intuition team.

Verification

  • tsc -b clean; new files lint clean (one pre-existing error remains in
    predicate-select.tsx, untouched here)
  • All 5 routes render headless with no console errors, in both wallet and read-only builds
  • All 36 canonicalKey references and 36 classification slugs validated against the
    installed packages
  • Candidacy GraphQL query validated against the live mainnet indexer

Not included, deliberately

Minting the 25 enshrined predicate atoms and the registry root. Whoever does that defines
the on-chain root of the shared vocabulary — a decision for the Intuition team, not a
contributor. See plan.md §9 Q6.

🤖 Generated with Claude Code

lutin and others added 24 commits May 20, 2026 22:08
- Connect wallet in header, enforce chain 1155 with switch prompt,
and document env / Node version requirements.
Show ENS
Refactor my first commit
rename glossory to protocol ( all terme)
Changed additive filter to a funnel
pnpm-workspace.yaml was committed without a `packages:` key, so pnpm
treated the repo as a monorepo and aborted with "packages field missing
or empty" before resolving any dependencies. This is a single package,
not a workspace, so the file is removed; its `allowBuilds` block (which
held literal placeholder strings, not booleans) becomes a valid
`pnpm.onlyBuiltDependencies` entry in package.json.

Also:
- drop package-lock.json so the package manager is unambiguous — nixpacks
  already selects pnpm from pnpm-lock.yaml
- pin packageManager to pnpm@9.15.9 to match the deploy image
- declare @0xintuition/protocol, which intuition-chain.ts imports but
  which only resolved transitively

Verified with `pnpm i --frozen-lockfile && pnpm build`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…egistry

Claims created in Ontology could not join the shared graph. The app minted
predicate atoms by hashing a bare label — createAtomFromString(config, 'created
by') — while the canonical registry hashes a JSON-LD DefinedTerm document. Those
are different bytes, so they produce different atom ids: every predicate atom
this app wrote was invisible to the rest of the ecosystem, and vice versa.

The canonical derivation did not exist when Mission 01 was built. This is drift,
not a defect in that work.

Canonical identity
- src/lib/intuition/canonical.ts is the single boundary to the alpha
  @0xintuition/* packages, so an alpha bump has one blast radius
- predicate atoms now carry canonical DefinedTerm bytes
- writes preflight the deterministic id against the indexer and reuse the atom
  instead of minting a duplicate — free, synchronous, no wallet needed
- the minted id is asserted against the precomputed one; a mismatch throws
  rather than silently staking against the wrong atom

Vocabulary reconciliation
- 56 predicates: 29 direct, 7 direction-flipped, 20 proposal candidates
- 39 entity types: 32 direct, 4 renamed upstream, 3 app-local
- directions were verified against each canonical predicate's description, not
  inferred from its name — `founder` reads organization→person, so this app's
  `founder of` maps to `founded`
- flips implement mint-time canonicalisation locally: <A,employs,B> and
  <B,employedBy,A> are one fact hashing to two vaults, which upstream audit A1
  calls economically unsound. Their fix (item A8) has not shipped. The builder
  discloses the swap before staking.

Predicate registry
- propose via <candidate> — listedIn → <predicateRegistry>, ranked by net stake
- mints the registry atom when absent; it is unminted on both networks today
- candidacy is deliberately a different edge from ratified membership, which
  upstream records as <registry, contain, X>. A proposal is not yet true.

Read-only mode
- WalletProvider previously rendered ONLY a warning banner when
  VITE_PRIVY_APP_ID was unset, replacing the entire app. An unconfigured
  optional integration took down everything that never needed it.
- the read stack now sits outside the wallet stack; wallet hooks select an inert
  implementation at module load from the build-time constant
- read-only bundle is 858 kB vs 5.6 MB — Privy and wagmi tree-shake out

Bundle
- vendor splitting + route-level lazy loading
- entry chunk 3,221 kB -> 186 kB

Docs
- README replaced (was still the Vite template)
- .env.example documents that VITE_* are build-time only, with the Coolify
  build-variable requirement that caused the preview deploy to ship an
  env-less bundle
- plan.md records the analysis and open questions for the Intuition team

Verified: tsc clean, new files lint clean, all five routes render headless with
no console errors, in both wallet and read-only builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@host-intuition-box

host-intuition-box Bot commented Aug 5, 2026

Copy link
Copy Markdown

The preview deployment for Ontology is ready. 🟢

Open Preview | Open Build Logs | Open Application Logs

Last updated at: 2026-08-05 15:30:25 CET

jeremie-olivier and others added 5 commits August 5, 2026 16:13
The wallet build shipped a blank page:

  Uncaught ReferenceError: Cannot access 'wr' before initialization
    at /assets/vendor-query-*.js

Splitting vendors by package (vendor-react, vendor-query, vendor-chain, …) cut
chunk boundaries through cyclic imports between those packages. Rollup emitted
chunks whose cross-chunk initialisation order is not satisfiable, so the entry
threw before React mounted.

The failure only reproduced in the wallet build — the read-only build has a
sparse enough dependency graph to avoid the cycle. That is exactly the gap in
how this was verified: the headless smoke test ran against a read-only build,
then a wallet build was shipped untested.

Chunking is now left to Rollup. Route-level lazy() stays, because those
boundaries are derived from real dynamic imports rather than guessed.

Entry chunk is 3,087 kB rather than the 186 kB the broken config produced.
Reducing it further means lazy-loading the Privy subtree behind Suspense, which
is a real change to first paint and belongs in its own PR with its own
verification.

Verified: all five routes render headless with zero uncaught errors in the
wallet build, not just the read-only one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three modes were presented as interchangeable data sources. They are
stages of one pipeline — try it on Testnet, argue it for real on Mainnet, and
if it reaches consensus it is adopted into the @0xIntuition packages, which is
what Standard reads. The distinction is load-bearing: the same action costs
nothing, costs TRUST, or is impossible, depending on where you are.

Modes
- ordered Testnet → Mainnet → Standard everywhere, matching the journey
- Standard is now the default; a first-time visitor lands on the settled
  read-only vocabulary rather than a live network where actions cost TRUST
- the switcher shows the mode name on the trigger, not just in the menu, and
  each option carries its step number and purpose. Which mode you are in
  changes whether an action spends money, so it must never require opening a
  dropdown to check.
- src/lib/wallet/ontology-modes.ts is the single source for order and copy

Standard mode now reads the packages, not src/data
- Entity Hierarchy: 37 classifications grouped into the package's own 7
  categories, with field counts
- Entity Schema: field-level spec — labels, types, required flags — plus the
  schema.org link and recommended predicates
- Entity Relationship: typed relationships from the metadata-predicate matrix
  (`director → person`), falling back to the classification's own
  metadataPredicates where the matrix has no entry. The two are shown
  distinctly: an untyped row is a gap in the standard, which is itself worth
  proposing a fix for.
- Predicate Explorer: all 133 predicates by category, filterable by status,
  showing enshrined vs proposed. Status is the headline — staking on a proposed
  predicate is a vote about the vocabulary.

Nothing above is hardcoded. Bumping the packages and redeploying is the entire
update process.

Contribution path
- each section carries a propose invitation. Standard is read-only by
  definition, so proposing switches to Mainnet and routes to the right surface
  (builder, matrix, or registry) rather than dead-ending.
- home links to github.com/0xIntuition/packages

Verified: tsc clean, new files lint clean, headless render shows 37 types across
all 7 categories and "133 predicates — 25 enshrined, 108 proposed", zero
uncaught errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A proposal fills a hole in a known shape rather than inventing a sentence, and
there are only two holes worth arguing about:

  Person — has type — ?     object open: what can be said about a type
  Person — ?  — Company     predicate open: how two types connect

Candidates compete for the same slot, which is what makes the outcome
decidable — stake on them is comparable rather than scattered across unrelated
claims.

The relationship panel is real package data: it picks the most contested pair
for the selected entity and shows the predicates genuinely competing for it
today (Person → Company draws member of, employed by, affiliated with). The
describe panel's objects are labelled illustrative, because a `has type` object
is user-supplied by definition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A proposal's stake total says how much conviction exists; it does not say
whose. For a vocabulary decision that distinction matters — one whale and
twelve independent builders are very different signals at the same TRUST
amount — so this reads individual positions rather than the aggregate.

- overlapping avatar stack answers "is anyone behind this?" without a click,
  with +N overflow past four
- hover, focus or tap opens the full scrollable breakdown: who agrees
  (positions) and who disagrees (counter-triple positions), each with their
  stake and a running total
- dissent is shown by ring colour in the stack rather than hidden, so a
  contested proposal is visible while scanning
- accounts without an avatar get a colour derived from their address, so the
  same backer is recognisable across proposals before reading the label
- redeemed positions (zero shares) are filtered out — they are no longer
  backing anything

The stack sits outside the DialogClose button: opening the list to read who
staked must not count as picking that predicate.

Verified against real testnet data (14 slots, 18 proposals with backers);
mainnet has no slot proposals yet, so the component renders its empty state
there. All routes render headless with zero uncaught errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three additions to the slot proposal dialog, alongside the backer stack.

Portal link
- opens the nested triple on the Intuition Portal, following the active
  network: portal.intuition.systems on mainnet, dev.portal on testnet
- portal base URLs live in the network definition next to graphqlUrl, so the
  link can never point at the wrong chain
- renders nothing in Standard mode — package data has no on-chain term
- this app shows a proposal in ontology terms; the Portal is the canonical
  place for position history and share price. Link out rather than reimplement.

Agree / disagree
- thumbs deposit at the protocol's `minDeposit`. This is a signal, not a
  position size — the Portal link alongside is where real exposure belongs.
- disagreeing deposits into the *counter-triple*, a distinct term with its own
  vault, whose id is read from the contract rather than assumed. Without a
  first-class dissent action a contested proposal is indistinguishable from an
  ignored one, which defeats settling vocabulary by stake.
- uses multiVaultDeposit directly: the SDK's `deposit` wrapper drops the
  `value`, so it would deposit nothing
- invalidates the backer query on success so the avatars update in place

Propose your own
- always visible, not only when the slot is empty — adding a predicate is the
  point of the dialog
- prefills the builder with both entity types and an empty predicate, which is
  precisely the hole being proposed into
- from Standard mode it switches to Mainnet first, since there is no builder to
  land in there, and says so on the button

Vote and link handlers stop propagation: the row is a predicate picker, and
staking or opening the Portal must not also select it.

Verified: tsc and lint clean, all routes render headless with zero uncaught
errors. The dialog body itself is portal-mounted on open, so its contents are
not covered by the headless DOM check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

4 participants