Drift — where AGENTS.md and the code disagree (code wins)·
| # | AGENTS.md says | Code says | Consequence for the template |
|---|---|---|---|
| 1 | Worker router lists ~13 endpoints (upsert/get/retrieve/list/community/profile/share/legal/user/credential) | src/index.ts registers 121 endpoints across 46 modules + cron + email + Durable Object |
Parity ledger is built from index.ts, not the doc |
| 2 | Storage: "Vectorize: VECTOR_INDEX \"tiny\"" | wrangler.toml: index_name = "tiny-v2" (binding VECTOR_INDEX), plus MEMORY → memory |
Bootstrap script must create tiny-v2-shaped index, name parametrized |
| 3 | KV namespaces: tiny, post, applause, stats | toml also binds tiny_old; applause and tiny_old have 0 references in src/ |
Candidates for removal in template (verify with tests before dropping) |
| 4 | Secrets: OPENAI_API_KEY, INTERNAL_API_KEY, CLOUDFLARE_API_TOKEN | src also reads DEPOSIT_ADDRESS, VAPID_{PUBLIC,PRIVATE}_KEY, VAPID_SUBJECT, MODEL_CONFIG_ENC_KEY, BASE_RPC_URL, BASE_SEPOLIA_RPC_URL, RECONCILE_ALARM_USER | All go in .env.example + wrangler.toml secrets doc |
| 5 | "no Node-only APIs in app/api/*" (edge) | 8 routes export runtime = 'nodejs': chain/status, devices/ask, devices/endpoint/chat, job-run, run-tool, wallet/faucet, wallet/withdraw, x402/pay (INVENTORY §12) |
Template keeps the split; docs state which routes need Node (viem signing, new Function sandbox, long deadlines) |
| 6 | Directory map omits app/{calls,chain,devices,map,universe,voice,wallet,wearables} pages and components/{Map*,Universe*,Profile*} |
They exist (20 pages/routes, 44 components) | R9 must port them; charter says NO feature cut |
Process incidents (builder's own)·
| Date | What happened | Verdict | Fix |
|---|---|---|---|
| 2026-09-15 iter 3 | Commit 23dd774 was pushed although gitleaks printed leaks found: 1 — the check was chained through tail, which swallowed the exit code |
False positive: .env.example X402_QUOTE_SECRET= (empty) matched with the following variable name as the "secret". No value was ever present. 18 further dir-scan hits were git-ignored .next/ build output |
scripts/preflight-push.sh scans only tracked files + history and exits non-zero on any finding; .gitleaks.toml allowlists empty-value lines in .env.example only; CI runs the same config |
| 8 | lib/tiny-record.ts + app/[slug] send Authentication: Basic base64(tinyai:tinyai) to the worker /get |
No worker module reads an Authentication header (0 hits in chatgpt-plugin-tinyai/src) — a dead artifact of the pre-platform proxy |
Dropped in the template; the header carried no secret |
| 9 | Worker not-exists sentinel is the literal string 'tiny.technology is not exists' |
It is a wire constant clients classify on (isTinyNotExists), so it cannot be renamed without a client roll-out |
Kept byte-identical, lifted into @tiny-vercel/contracts (TINY_NOT_EXISTS) so app and worker share one definition |
| 10 | /api/udid isOwnerLogin defaults to the author's GitHub login when OWNER_LOGIN is unset |
Owner-specific constant (charter rule 4) | Template default is no owner (roster unreachable until OWNER_LOGIN is set); the source's isOwnerLogin('cagataycali') === true assertions are inverted in the ported test and say why |
| 11 | Worker oauth.ts (per-service OAuth token store, migration 0015, 4 route classes) is documented as the backend for use_github/use_spotify/use_google |
No router.* line in index.ts mounts it and no app route calls it — the module is dead in production; only tests/oauth-sql.test.ts exercises its SQL |
Ported verbatim + test green, not mounted (mounting would add a surface the source never exposed). Owner question D-009: wire /oauth* + build the /api/auth/<service> callbacks, or delete the module |
| 12 | Worker media.ts is a self-contained module |
It imported parseByteRange from voice.ts (1159 lines, R8) |
Extracted the 20-line function verbatim into src/http-range.ts; voice.ts imports from there when it lands (deliberate) |
| 13 | Worker ask.ts calls the app's /api/job-run |
Both call sites hardcoded https://tiny.technology/api/job-run |
Now ${siteUrl(env)}/api/job-run (APP_URL var) — a self-hosted deployment would otherwise have asked the author's production site to run its jobs |
| 14 | DM push says where to reply | messages.ts push body hardcoded Reply at https://tiny.technology/… |
${siteUrl(env)}/… |
| 15 | Web routes read the worker URL from TINY_WORKER_URL |
13 device/media/message routes each redeclared WORKER_URL = process.env.TINY_WORKER_URL \|\| '<production host>' |
Single workerUrlOrPlaceholder() in lib/config; unset → .invalid host + /api/health flag, never the author's worker |
| 16 | Worker scheduler.ts runs due jobs "through the app's chat pipeline" |
Callback hardcoded https://tiny.technology/api/job-run |
${siteUrl(env)}/api/job-run; apps/worker/tests/scheduler-callback.test.ts pins that the callback goes to APP_URL and that the source contains no production host |
| 17 | job-abandoned.test.ts says "every surface styles job_missed distinctly" (iOS, Android, tiny-tech tray) |
Those surfaces are separate clients the template does not carry | Ported with only the web surface (lib/chat/prompt.ts) — a "split" per docs/TESTS.md |
| 18 | model-config.ts / model-providers.ts encrypt BYO API keys "AES-256-GCM at rest" |
No unit test existed for encrypt/decrypt; wrong-key decrypt silently returns '' (by design — degrades to default provider) |
Pinned in apps/worker/tests/model-keys.test.ts; MODEL_CONFIG_ENC_KEY documented in docs/ENV.md with the rotation caveat |
| 19 | /api/tools, /tools/install, /tools/run call "the sandbox validation hop" at the app itself |
Hardcoded NEXT_PUBLIC_APP_URL || 'https://tiny.technology' — a fork without the env var would validate user code against the upstream site |
appOrigin(req) (env or the request's own origin); /api/tools/trust hit plugin.tiny.technology/prefs directly → workerUrlOrPlaceholder() |
| 20 | Worker tools.ts "marketplace" (browse/list/upsert/delete) |
No worker-side test existed; only the web lib/user-tools sandbox was tested |
apps/worker/tests/tools-marketplace.test.ts on real SQLite: key gate, validation, upsert-in-place, owner-scoped delete, LIKE-escaped browse with author join |
| 21 | Worker telegram.ts runs each inbound message "via the app's job pipeline" |
fetch('https://tiny.technology/api/job-run') hardcoded — a fork's bots would have sent their owners' prompts (and INTERNAL_API_KEY) to the upstream site; the pairing reply also named tiny.technology/<slug> |
siteUrl(env) / siteHost(env) from APP_URL; pinned by apps/worker/tests/telegram-poll.test.ts (pairing, authorized turn, stranger, offset CAS) |
| 22 | Worker firmware.ts FIRMWARE_HOSTS — "where our artifacts are served" |
Hardcoded ["plugin.tiny.technology", "tiny.technology"]: a fork's /firmware/publish would refuse the fork's own artifact URLs and accept upstream's |
firmwareHosts(env) = hosts of APP_URL + optional WORKER_URL (new var, docs/ENV.md); empty list refuses every publish; validateBundle(raw, hosts) takes the list explicitly |
| 23 | Upstream tests/firmware-channel.test.ts "a second publish REPLACES the channel" |
RED in tinyai-id itself: fixture version ota-second00000 ranks below ota-deadbeef1234 under versionOrder, so the downgrade guard (Aug 25) turns the second publish into a 409 the test never checks |
Ported with a genuinely newer version and status assertions on both publishes; the "REPLACES" property is now actually exercised |
| 24 | Web /api/voice/session persona: "a living AI at tiny.technology/${name}" |
Hardcoded host in the spoken system prompt | ${siteHostName()}/${name} from lib/config |
| 25 | Worker legal.ts — Terms of Use text |
Operator's brand hardcoded ~12 times in a legal text served to every fork | Brand = siteHost(env); header comment marks it TEMPLATE TEXT to replace before going public; tests/legal.test.ts pins both |
| 26 | Worker /pay/* (19 routes) + cron reconcilers |
Always on; a fork with no deposit address/facilitator would expose a half-configured money path | Mounted through pay(...) gate: 404 payments disabled + reconcilers skipped unless PAYMENTS_ENABLED="true" (tests/payments-gate.test.ts). Source suite x402-reconcile-status pins the gated registration string. |
| 27 | lib/x402/top-up.ts investor/reach copy · lib/deadlines.ts comment |
Named the upstream operator ("no tiny.technology account needed"; "plugin.tiny.technology straight from the browser") | Brand-free copy ("no account on this site needed"); top-up.test.ts pins the new string |
| 28 | Web money routes /api/wallet{,/faucet,/withdraw}, /api/x402/{pay,chat/[slug]}, /api/chain/{join,status}, /api/erc8004/registration/[slug] |
const WORKER = 'https://plugin.tiny.technology'; x402 resource/description, headless fetch('https://tiny.technology/api/chat'), service, erc8004 image/web/x402-chat endpoints all hardcoded tiny.technology; pay-route allowlist + first-party hosts ['tiny.technology','plugin.tiny.technology'] |
workerUrlOrPlaceholder(); appOrigin(req)/siteHostName(); paymentRequirements(slug, price, payTo, origin = appUrl()); firstPartyHosts() = hosts of NEXT_PUBLIC_APP_URL + TINY_WORKER_URL (no default host); every handler opens with the PAYMENTS_ENABLED 404 gate (tests/payments-gate-web.test.ts) |
| 29 | chain/ |
1.3 MB operator lane: Besu multinode genesis/bootnodes/artifacts, facilitator server, e2e scripts, backups | Only what routes import: apps/web/chain/dev-keys.mjs (Anvil's public dev keys as a DENYLIST — gitleaks allowlisted by path), settle-outcome.mjs, contracts/TinyUSDC.sol, multinode/genesis-8470.json (tinyai-id's genesis as the sample a tiny-network operator replaces). Suites needing the rest (chain-join, chain-join-doc, chain-calldata artifacts, chain-identity/rpc-proxy) not ported. |
| 30 | Worker email() entry point |
tiny@ → hardcoded personal iCloud address; Cloudflare account id 6a90286f… inlined in the destination-registration fallback; cagataycali@… |
src/email.ts: EMAIL_OWNER_FORWARD (unset = reject), CLOUDFLARE_ACCOUNT_ID+CLOUDFLARE_API_TOKEN (unset = skip registration); postmaster@/hello@ join tiny@; reject sentinel from contracts pkg; 9 tests |
| 31 | components/MapBackground.tsx |
Google Maps browser key AIzaSy… inlined as the fallback for NEXT_PUBLIC_GOOGLE_MAPS_API_KEY |
env only; unset → loadMapsApi() resolves false, map never loads (no fallback key in the template) |
| 32 | app/layout.tsx, robots.ts, sitemap.ts, manifest.ts, error.tsx, global-error.tsx |
metadataBase https://tiny.technology, @tinyaid player card, say.jpeg/tiny.mp4, publisher "Formaticai.com.", keyword list, https://plugin.tiny.technology/community in the sitemap, unconditional @vercel/analytics, "tiny.technology hit a snag" prose |
lib/site.ts builds metadata from NEXT_PUBLIC_SITE_*; base URLs from NEXT_PUBLIC_APP_URL; sitemap via TINY_WORKER_URL; Analytics opt-in NEXT_PUBLIC_VERCEL_ANALYTICS=1; prose uses siteName(). SEO keyword list dropped (operator copy). |
| 33 | components/chat/{Chat,CommandPalette,UniverseDrawer,Control}.tsx, lib/chat/slash-commands.ts, app/page.tsx |
browser-side https://plugin.tiny.technology/{community,get,list,tools/browse,openapi.json}; tiny.technology/<slug> prose in the claim CTA/hero/export; A2HS QR pinned to https://tiny.technology/ |
lib/public-config.ts: publicWorkerUrl() (NEXT_PUBLIC_TINY_WORKER_URL), publicSiteHost() / publicAppUrl() (NEXT_PUBLIC_APP_URL). Behaviour unchanged. |
| 34 | R9c pages: app/[slug], about, calls, map, og, universe (+opengraph-image), vcard, wallet, wearables; components/{Community,Profile} |
https://tiny.technology origins in OG/JSON-LD/vCard/x402 share URLs; @tinyaid twitter site/creator; say.jpeg 1920×1080 OG image; siteName: 'tiny'/'tiny.technology'; plugin.tiny.technology server fetches (Profile fell back to it when TINY_WORKER_URL unset); ${name}@tiny.technology vCard email; tiny.technology is not exists retyped sentinel |
lib/site.ts +siteOrigin()/siteOgImageAbsolute(), siteTwitter() (absent → no handle), siteName(), siteHostName(); server files workerUrl(), client files publicWorkerUrl()/publicAppUrl()/publicSiteHost(); sentinel imported from @tiny-vercel/contracts. Behaviour unchanged. |
| 35 | tests/wearables-web.test.ts |
pins the owner's Apple team ID / Android cert fingerprint (AASA + assetlinks ↔ ios/*.pbxproj, android gradle) | not ported — the template ships no native app or .well-known app-link files; an operator adding them should add the test back with their own identities |
| 36 | tiny-tech/src/cli.ts:417 (tray ticker) |
GET /api/wallet/balance — a route that exists on neither tinyai-id nor the template (/api/wallet GET is the balance route); the probe is wrapped in try {} catch {} so the tray simply never shows a balance |
ported verbatim in apps/cli (rule 3); the fix (/api/wallet) is a one-line follow-up for the CLI lane's owner review |
| 37 | tiny-tech/test/server.test.mjs:213 |
the unauthenticated MCP smoke test called the LIVE production worker (plugin.tiny.technology/list) — a network dependency on the operator's backend inside npm test |
apps/cli/test/server.test.mjs starts a loopback fake deployment (/api/health advertising itself as worker, /list, /retrieve) and points the CLI at it with TINY_API_URL; the test now also proves the worker was DERIVED from the app and cached in config.json |
| 38 | tiny-tech/src/daemon.ts:22 |
launchd label technology.tiny.daemon (reverse-DNS of the operator's domain) |
dev.tiny-vercel.daemon — also means a template daemon and an operator's tiny-tech daemon can coexist on one Mac |
| 39 | tiny-tech/src/tray.ts TrayStatus / menubar/main.swift:293 |
the menu bar opened https://tiny.technology for "the feed" |
status reply carries webUrl (the configured app origin; public, no token) and the Swift helper opens that; nothing opens when no daemon is running |
| 40 | tiny-tech/test/local-tools.test.mjs:93 |
the "uncreatable path" fixture was /proc/definitely/not/writable/tiny; on Linux mkdirSync(…, {recursive:true}) under procfs never returns, so npm test hung forever on the ubuntu runner (upstream's own CI matrix is ubuntu — the suite only ever passed on a Mac, which has no /proc) |
fixture is now a path under a regular file (ENOTDIR everywhere); mcp.test.mjs also asserted the macOS-only use_computer mounts in the child — now darwin-only, like the call assertion two lines below already was. Full suite verified on node:22-bookworm (arm64): 1445 tests in 24 s |
2026-09-17 — apps/ios and apps/android imported from tinyai-id·
Imported with git subtree add (history preserved: 665 iOS commits, 590 Android commits).
Changes made at import so the template stays deployer-neutral:
- Android: Google Maps key and Meta Wearables DAT creds moved out of
AndroidManifest.xml/build.gradle.ktsintolocal.properties/ env (MAPS_API_KEY,MWDAT_APP_ID,MWDAT_CLIENT_TOKEN), seelocal.properties.example. The historical commits still contain the (package-restricted) Maps key; fingerprints are allow-listed in.gitleaksignore. - iOS:
DEVELOPMENT_TEAMand the MWDAT creds moved fromproject.ymlintoSecrets.xcconfig(gitignored,Secrets.example.xcconfigcommitted);Info.plistnow carries$(MWDAT_APP_ID)/$(MWDAT_CLIENT_TOKEN)which Xcode expands at build time.Tiny.xcodeprojregenerated from the example values.
~~Still drifting from charter rule 4: both apps hardcode the production base URL
(https://tiny.technology, plugin.tiny.technology) in source.~~ Resolved 2026-09-18:
both apps take TINY_BASE_URL + TINY_WORKER_URL at build time (iOS Secrets.xcconfig →
Info.plist TinyBaseURL/TinyWorkerURL → Config.baseURL/Config.workerURL; Android
local.properties/env → BuildConfig → net/TinyApi.kt BASE_URL/WORKER_URL, wear too).
Every URL the apps build — API, sign-in, x402/ERC-8004, OTA manifest, worker media/recordings/
profile/community, and the host named in copy — derives from them; the production values remain
only as documented fallbacks (Config.swift, the two build.gradle.kts, the example files) so
the reference build is byte-for-byte the same product. Still deployer work, not code: associated
domains (applinks:), hosting the OTA artefacts under your TINY_BASE_URL, device firmware
media allow-lists. Guide: docs/guides/mobile-apps.md.
Amended 2026-09-18 (same day, apps-origin lane) — the resolution above carried TWO settings,
which broke the rule the README states for every client (one setting, discovered once, docs/CLI.md).
Now: TINY_BASE_URL is the only required setting; the worker origin and site name are read at
launch from GET <TINY_BASE_URL>/api/health (workerUrl, siteName — the JSON
apps/web/app/api/health/route.ts publishes and apps/cli/src/config.ts probeDeployment reads),
cached per base URL (iOS Deployment in Config.swift, app-group container; Android
net/AppConfig.kt, SharedPreferences) and refreshed on every foreground. TINY_WORKER_URL is kept
in both example files as an explicit override only (empty by default). Fallback when nothing has
been discovered: the reference worker only when the base is the reference deployment, otherwise
the base itself — a fork's traffic never reaches the upstream operator's worker. Pinned by
apps/ios/Tests/DeploymentTests.swift and apps/android/app/src/test/.../net/AppConfigTest.kt.