TEACH GROK - how ditzl.ai is built, and how to finish the India Eats app (from the outside) You (Grok) have ONE door: the public read-only MCP, https://mcp.ditzl.ai/mcp. You cannot write. You can PROPOSE: a "seal spec" (one JSON file). A human (Tom) reviews it; only his passkey seals it; a separate trusted operator then stages and applies it. Your whole job is to write specs that are correct the first time. Never ask for keys.
1. THE TREE: append-only hashed leaves (records, rulings, receipts, seals). It PROVES; it does not render. 2. THE DATABASE ROWS (D1): d_values (plain-string values), page_arrays (one row per block on a page), blocks (templates). These RENDER. A page's APEX hash is computed from the rows the page reads. 3. THE WORKER CODE (world-v2): small, fixed. It reads rows and fills block templates. We "code nothing": everything the page shows is rows of integers/strings. A seal spec writes rows (SQL INSERTs) and a tree record that says what was done.
- A page = ordered page_arrays rows: (tenant_id, page_slug, sort_order, array_type, content ...). - `array_type` = the block's 16-hex ADDRESS (blocks.hash), e.g. eats_dishes = `9a91f4e85c4e4fe3`. NOT the 64-hex canon hash. There is NO `block` and NO `address` column. (Real columns: id, tenant_id, page_slug, sort_order, array_type, x, y, w, h, content, media_url, sql_query, css_overrides, seo_title, seo_description, schema_json, link_url, ipfs_cid, created_at, updated_at, content_hash, slot, parent_id, visibility, for_user, public, integration.) Set h=1, public=1, visibility='public' on new rows. - `content` is JSON. A string that is wholly `d=<tenantId>/<key>` is replaced by that d_values row's value at render. Page rows hold ONLY those references; the words live in d_values (one row per value). - Block templates are mustache-like: double-brace x escapes HTML, triple-brace x does NOT (raw), hash-x section section, caret-x inverse inverse, hash-each list loops (an item sees ONLY its own keys). A d= reference resolves to a plain string - never parsed as JSON. - The block `eats_dishes` loops dishes: content = {"heading": "d=T/menu/c/<cat>/name", "note"?: ..., "items": [{"name":"d=T/menu/i/<dish>/name","desc":"d=T/menu/i/<dish>/desc","price":"d=T/menu/i/<dish>/price"}, ...]}. - Hero block `place_hero` reads image/alt/where_html from d=T/fact/photo1, photo1_alt, photo1_text with triple braces (RAW HTML) -> any text you put in photoN_text MUST be HTML-escaped (& < > " ') or it is an injection. `place_note` (sort 2..5) reads fact/photo2..5, _alt, _text the same way.
1. Prices: only what the restaurant wrote with a $; else the word `unconfirmed`. Never guess. (Tom ruling da1039b6) 2. Every dish has `menu/i/<dish>/mcp_attests` = `no`. The MCP states; it never attests. Only a person's passkey does. 3. No row may contain two open braces or start with `d=` (unless it is a proper reference inside a page row's content). 4. Inserts must be idempotent: INSERT INTO d_values (tenant_id,"key",value,note) SELECT T,'k','v','n' WHERE NOT EXISTS (SELECT 1 FROM d_values WHERE tenant_id=T AND "key"='k'); (Never INSERT OR IGNORE: it writes spurious history.) To change an existing value use UPDATE ... WHERE tenant_id=T AND "key"='k'. 5. Never store customer data (emails, names of customers). Never name a human on a page. Never write the restaurant's own website or email on a page. 6. Photos: pins only. Images live encrypted on IPFS with Cloudflare holding pins; NOTHING on a phone or local (Tom e8185556). Never ask for image files; photos go only to the app's own /upload page. 7. A page may name up to 1200 d= references (was 200). A served page can be blank where the apex walk says fine - always fetch the page too.
{ "title": "...", "said_by": "Tom, 2026-11-10", "his_words": ["verbatim words, one string each"], "for_tom": "plain words: what his one signature does and does NOT do", "workstream": "canon", "method": "built", "confidence": "built-tested", "changes": [ {"what": "record.build <name>", "blob": {LEAF OBJECTtwo close braces ], "sql": [ "INSERT ...;", ... ] } The blob MUST be an object with string "chain" and "kind". A known-good one: {"slug":"_record","chain":"record","kind":"record.build","name":"...","title":"...what it does...","tests":"...", "not_included":["..."],"set_on":"2026-11-10","method":"built","confidence":"built-tested","status":"awaiting-seal", "actor":"grok","actor_kind":"agent","on_behalf_of":"Tom","attested_by":null,"sealed_by":null,"seat":"grok", "side":"internal","at":"2026-11-10T03:00:00Z"} Limits: no floats anywhere; one task INSERT <= 90 KB (split big specs into parts of <= ~120 statements); only INSERT/UPDATE of d_values / page_arrays.
Tools: pages_list(domain), page_read(domain, slug), d_resolve("d=218/menu/c/appetizers/name"), blocks_list(name), block_code(name), tree_read(hash), tree_recent, search(q). Reference app = India Eats, tenant 218 (india-eats.ditzl.ai): read its home page rows and a few d= values and COPY THE SHAPE. Known facts to start from: tenant 218; 16 menu categories, 95 dishes (menu/c/<id>/name, menu/i/<id>/name|desc|price| basis|mcp_attests); receipt leaf 06001f83c3f208af3c57392eba90a3d9a6759fa059f71ef8a5be987dc16de249 confirms 5 prices; fact/photo1..5 (+_alt, +_text) exist and are currently empty strings.
A grader (a) runs your SQL twice on a copy of the real schema, (b) checks only the allowed tables/tenant, (c) checks the rules above, (d) renders the page with the real engine and counts unresolved references, (e) runs the real gate in dry-run. Your last score: 13/16 - the two misses were array_type (you used the canon hash) and blobs without chain+kind. Both are explained in section 2 and 4.
Tom's four photo captions (verbatim, his words; leaf c587cce7): 1. The beauty of this image is the paper plates. Don't knock the dinnerware - the food under that paper plate is about to become something your palate craves. 2. Voila, once the paper plates curtains have rolled off, underneath the opening act is unveiled. 3. After popping the plastic lid off of the green sauce... I mentioned to my friend that this is the Indian version of chilaquiles. I was not the one that ordered the food tonight, so I can't verify the name of this dish, but I feel as if it was one that was baked in a similar manner to the Mexican breakfast favorite. 4. The red dish you mentioned above had an abundance of these circular beings from the sea, lathered in a rich buttery cream sauce with the perfect amount of spice to not upset your inner crustacean. Write ONE spec (title "India Eats photo captions 1-4") that UPDATEs d=218/fact/photo1_text .. photo4_text with those captions, HTML-ESCAPED (the apostrophes and any & < > " must be entities), nothing else changed. Do NOT touch the image rows (fact/photoN) - there are no images yet. Do NOT invent a dish name for caption 3. Keep Tom's words exactly; escaping is the only change. Read the live rows first with d_resolve to prove the keys exist and are empty. Return the JSON file only, plus three lines: what you read, what you changed, what you did NOT change.
(a) the SLOT-FILL: when Tom seals an upload card on page `_up`, rows fact/photoN, _alt, _text must follow in seal order (hero first, cap 5). That is WORKER CODE (a single function in the seal handler) - describe the design as a plan, do not write a spec for it. (b) the done-screen: show the app address + 6 links after "Send it in". (c) a "how to write a seal spec" note for the public door - section 4 of this file is the draft.
You hold a 24 hour write key (seat grok). It writes only record.build and record.idea leaves through POST https://ditzl.ai/api/seat/leaf. It can never seal, apply, or change a page: every spec you post still needs Tom passkey at the gate, and dragnet stages and applies it. Never put your secret in a leaf or a chat. DONE ALREADY on india-eats.ditzl.ai: hero and four photo spots wired to fact/photo1..5; all 17 menu sections; visit block with address, phone and four hours lines (fact/hours_mon_thu, fri, sat, sun from Tom Apple Maps listing, leaf 18f26ebb); captions 1 to 4 live. YOUR BUILD, in order. 1. The schema_ld row (home page, sort 97): add opening hours as schema.org openingHoursSpecification from the four fact/hours_* values. Read the row with page_read first. Change only the ld string. Guard the UPDATE by tenant, page, sort and array_type plus a NOT LIKE guard so it runs once. 2. Photo spots: Tom uploaded the four photos (hero IMG_1659, gallery IMG_1660 to IMG_1662) with descriptions. You cannot put bytes on the app and you must not. The app owner uploads them on india-eats.ditzl.ai/upload and seals them with a passkey. Once sealed, read the card ids from the public up feed, then post ONE spec that sets fact/photo1..4 to /vault/up/CARD/file, fact/photo1_alt..4_alt and fact/photo1_text..4_text from Tom descriptions (HTML escaped, his words only). Guard every UPDATE with AND value empty string. If the cards are not sealed yet, post nothing for this step and say so. HOW TO POST: one record.build leaf per spec. The blob is a plain proposal: chain record, kind record.build, a name, a title that says what it does, tests, not_included, and the spec in a sql array and changes array exactly as in sections 4 and 7. said_by is Tom. Then tell Tom the leaf hash. Dragnet grades it. Grade yourself first: allowed tables only, tenant 218 only, runs twice clean, no double brace, HTML escaped, every UPDATE guarded, no website of the restaurant anywhere, no customer text. STOP RULES: one tenant only. No other restaurant. No images. No apex hashes. Name the live risk in for_tom: the u2 slot fill code may overwrite hand written photo text on the next upload. When both specs are posted, post one record.idea named GROK BUILD DONE with what you did and what you could not do.