Menjalankan dan merawat Blindband
Terbuat dari apa, bagaimana menjalankan satu ronde, dan apa saja yang rusak saat membangunnya.
Menjalankan satu ronde
Lima perintah, berurutan. Masing-masing menolak lanjut bila perintah sebelumnya meninggalkan sesuatu yang tidak konsisten.
- 01
npm run deployRegisters the component and re-scopes both sealed maps to every contract id this tenant has ever held. Idempotent — a second run costs nothing.
- 02
npm run submit -- data/records.jsonOne execution carries the whole file. Rejected rows come back with an index and a reason; accepted rows come back as receipts.
- 03
npm run round -- 2026-q1Aggregates inside the enclave, applies the four gates, and writes the response to data/round.json byte for byte.
- 04
npm run anchorRecomputes the digest locally first and refuses to write anything if it does not match. Then posts it to Solana devnet as a memo.
- 05
npm run verifyOffline digest, on-chain anchor, and three receipt probes against the contract — one of them a forgery, so a verifier that only says yes is caught.
Susunan repositori
Tiga bagian, dipisah agar logika yang penting bisa diuji tanpa node, tanpa enclave, dan tanpa kredit.
- contract/src/model.rs
- Wire types and the ruleset constants. No host calls.
- contract/src/stats.rs
- Percentile maths. Pure, unit-tested.
- contract/src/policy.rs
- The four gates and the aggregation. Pure, unit-tested.
- contract/src/ledger.rs
- The only module that touches the host. wasm32 only.
- agent/src/lib/
- Session, invocation, digest and Solana helpers, each with the reasoning for its approach written at the top.
- agent/state.json
- The only state outside git: tenant DID, contract ids, map names. Reproducible with one deploy.
- web/
- This site. Reads the round file off disk at build time so the bytes in your browser are the enclave's bytes.
Bug dan catatan platform
Semua di bawah ini memakan waktu nyata. Ditulis supaya orang berikutnya bisa memakai waktunya untuk hal lain.
Bagian ini tetap berbahasa Inggris di semua bahasa agar bisa diteruskan ke Terminal 3 persis seperti adanya — pesan galat yang diterjemahkan tidak akan cocok dengan apa pun di log mereka.
- BB-01Platform
The flat invoke() path rejects a sandbox-claimed API key
- Gejala
- `invalid api key: malformed api key token`, returned for every request, regardless of body or headers.
- Penyebab
- `invoke()` expects an opaque `t3n_key_…` token issued by organisation agent provisioning. A key claimed from the community sandbox page is a hex secp256k1 key intended for the SIWE session flow. The two are not interchangeable, and the error names neither.
- Perbaikan
- Use the authenticated session instead: `tenant.contracts.execute(tail, { version, functionName, input })`. It costs one handshake per process, which is irrelevant for a round that runs quarterly.
- Biaya
- Roughly half a day, most of it spent assuming the request body was wrong.
- BB-02Platform
The sandbox trust manifest does not parse, so attestation cannot be verified
- Gejala
- `Trust manifest at https://cn-api.sg.testnet.t3n.terminal3.io/api/trust-manifest is malformed.` The SDK then refuses to proceed unless `T3N_ALLOW_UNVERIFIED_NODE=true` is set.
- Penyebab
- The manifest served by the sandbox control node is not in the shape the SDK expects. The SDK's behaviour is correct — it declines to trust a node it cannot attest.
- Perbaikan
- None available from the client side. Every run on the sandbox therefore carries an `unsafe_trust_server` warning, which the agent prints rather than suppresses.
- Biaya
- Not blocking, but it undercuts the demo: the one property a TEE platform sells is the property that cannot currently be checked on its own sandbox.
- BB-03Platform
A KV map's ACL names contract ids, and a version bump mints a new one
- Gejala
- After registering v0.2.0, every read failed with `kv_store.get on 'z:…:bb-rounds' read denied: access denied: TenantContract(did:t3n:…/871) cannot read map` — and the log ring came back empty, because the contract dies during instantiation before it can log anything.
- Penyebab
- The maps were created with `readers: { only: [870] }`, the id of the previous build. Registering a new version produces a new contract id, and the old ACL does not follow it.
- Perbaikan
- Track every id the tenant has registered in `state.json` and re-scope both maps to the union with `tenant.maps.update()` on each deploy. The method exists in the SDK but is not mentioned in the KV maps guide.
- Biaya
- The longest single block of the build. The empty log ring is what made it expensive — the failure gives you nothing to search for.
- BB-04Platform
A map created without `readers` succeeds, then denies every read
- Gejala
- `create` returns cleanly. Every subsequent `kv_store.get` fails with AccessDenied.
- Penyebab
- The KV governor defaults to deny. An omitted `readers` set is not an open map, it is a closed one.
- Perbaikan
- Always pass `readers` and `writers` explicitly at creation. Worth a line in the docs, since the failure surfaces far from the call that caused it.
- Biaya
- An hour, and it would have been more without BB-03 already teaching us to suspect the ACL.
- BB-05Platform
An execution locks half the sandbox allocation regardless of what it spends
- Gejala
- A sandbox allocation is 20,000,000,000 base units. Each contract execution locks 10,000,000,000 while it runs, though a real round settles at roughly 100–180,000,000.
- Penyebab
- The lock is a worst-case reservation, not a charge. It is released on completion.
- Perbaikan
- Batch aggressively. Submitting 117 rows one call at a time would need 117 executions; `submit-batch` does it in one for 5.9B. The constraint shaped the contract's interface, which is worth saying out loud in the docs.
- Biaya
- No lost time, but it silently rules out the obvious per-row API design.
- BB-06Milik kami
The verifier could not read the anchor it had just written
- Gejala
- `Memo on 5uczxVJU… is not a Blindband anchor.` — reported against a transaction that was, in fact, a Blindband anchor.
- Penyebab
- SPL Memo v2 echoes the payload into the program log as a JSON *string literal*: `Memo (len 157): "{\"p\":\"blindband\"…}"`. Slicing between the outer quotes yields text with the escapes still in it, which fails to parse.
- Perbaikan
- Read the decoded instruction data from `getParsedTransaction` instead of the log echo, and unescape properly in the fallback path. The instruction data is the committed bytes; the log is a convenience.
- Biaya
- An hour, and a genuinely alarming few minutes in which the tool appeared to be reporting tampering.
- BB-07Milik kami
A byte-order mark in state.json silently re-registered the contract
- Gejala
- `contract version invalid: version 0.2.0 is not higher than current version 0.2.0`.
- Penyebab
- `readState()` caught the JSON parse failure and returned `{}`, so the deploy believed nothing had been registered yet. The parse failed because the file had been rewritten from PowerShell, which adds a UTF-8 BOM by default.
- Perbaikan
- Write the file BOM-free. The deeper fix is that a state file which fails to parse should stop the deploy rather than being treated as an empty state.
- Biaya
- Half an hour of doubting the platform for something local.
- BB-08Milik kami
A default build target in .cargo/config.toml made cargo test impossible
- Gejala
- `cargo test` compiled everything, then died with `could not execute process … z_blindband-….wasm` / `%1 is not a valid Win32 application. (os error 193)`.
- Penyebab
- `.cargo/config.toml` set `[build] target = "wasm32-wasip2"` so the component build could be typed more briefly. That default applies to `cargo test` too: cargo built the test harnesses as wasm and then tried to execute them as native binaries.
- Perbaikan
- Delete the default target and name it on the one command that needs it — `cargo build --target wasm32-wasip2 --release`, which is what the deploy script already looked for. Plain `cargo test` now runs the 23 host tests.
- Biaya
- Small in minutes, large in consequence: the repository claimed the gates were covered by a command that did not run. Found by actually running every command in our own documentation before publishing.
- BB-09Milik kami
state.json was committed, so the first command failed for everyone but us
- Gejala
- A clean clone carries `agent/state.json` naming our tenant DID and contract ids 870/871. On someone else's key, `npm run deploy` prints `contract : reusing z:efd91540…:blindband (id 871)` — a namespace that key does not own — registers nothing, and scopes their two maps to `readers: { only: [870, 871] }`. The first `submit` then dies with AccessDenied and an empty log ring.
- Penyebab
- `.gitignore` covered `data/*.json` and `.env` but not `state.json`, and `deploy.ts` trusted whatever the file said without checking it against the tenant it had just connected as.
- Perbaikan
- Untrack `state.json`, and make the deploy compare `state.tenantDid` with the connected DID — foreign state is announced and discarded rather than used. An unparseable state file is now fatal too, which is the deeper fix BB-07 identified and did not make at the time.
- Biaya
- Nothing to us, which is exactly the problem: we only found it by cloning our own public repository and reading it as a stranger. It would have failed for every judge, in the most confusing way this platform offers.
- BB-10Milik kami
The sample data the README tells you to submit was not in the repository
- Gejala
- `npm run submit -- data/records.json` is the second command in our own quickstart. A clean clone has no `agent/data/` directory at all.
- Penyebab
- `.gitignore` held `data/*.json` with a `!data/.gitkeep` exception, and `.gitkeep` was never created — so git dropped the directory entirely. The rule was written to keep generated round and receipt files out, and it took the one input file with them.
- Perbaikan
- Ignore the four generated artefacts by name and track `records.json`. The 117 rows are synthetic — nine members named `member-01`…`member-09` — so there is nothing to withhold.
- Biaya
- Found in the same pass as BB-09. Between them, the pipeline was reproducible by nobody, while the repository claimed otherwise in three places.
- BB-11Platform
A sandbox tenant cannot provision the delegated agent identity the SDK offers it
- Gejala
- `client.createAgent(did, "blindband-round-runner")` on a sandbox-claimed tenant returns `RPC Error: organisation has no policy meta`. The method is present and callable; the tier is not.
- Penyebab
- `createAgent` takes an *organisation* DID and requires the caller to be one of its admins. A DID claimed from the community sandbox page is not an organisation and carries no policy metadata, so there is no organisation for the agent to belong to. This is the same tier boundary as BB-01, seen from the other side: the opaque `t3n_key_…` that the flat `invoke()` path wants is precisely what `createAgent` returns.
- Perbaikan
- None available from this tier. Rounds run under the tenant identity, and the agent reports which identity ran a round rather than implying otherwise. `npm run probe:agent` re-runs the check in one call, so whoever inherits this can tell in seconds whether their tier has lifted.
- Biaya
- No lost time — but it is the single largest gap between what this is and what an agent on this platform is supposed to be, and it is not closable by writing better code.
- BB-12Milik kami
The landing page was blank with JavaScript disabled
- Gejala
- A crawler, a reader with scripting off, or a screenshot taken before hydration got the header, the footer, and nothing in between. `curl` on the built page shows why: every section ships as `style="opacity:0;transform:translateY(14px)"`.
- Penyebab
- The one entrance animation on the site starts each section at zero opacity and animates it in when it scrolls into view. That initial style is server-rendered, so the page's whole argument — the round, the gates, the verifier — depended on JavaScript running to become visible at all. Nothing in the browser ever showed it, because in a browser the animation always ran.
- Perbaikan
- The animated wrapper carries a `data-reveal` marker and the document head carries a `<noscript>` rule that forces every one of them back to full opacity. The same commit made the light palette a `prefers-color-scheme` media query as well as a JavaScript-set attribute, so a light-configured machine gets a light page before hydration rather than a dark flash or nothing at all.
- Biaya
- Found while building the screenshot script, not while using the site — which is the point. Half of the captures came back as an empty gradient, and the flaky tool turned out to be the honest witness.
Melanjutkan atau menyerahkan
Saya ingin terus menjalankan ini dan membawanya ke pilot konsorsium sungguhan. Kalau Terminal 3 lebih memilih meng-host-nya, serah terimanya ringan: repositorinya mandiri, skrip deploy-nya idempoten, dan satu-satunya state di luar git adalah DID tenant, identitas kontrak, dan daftar akses peta — semuanya tercatat di state.json dan bisa dibentuk ulang dengan satu kali deploy.