ordo-pow 0.0.0-stage → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -2
- package/bin/ordo-pow.js +9 -0
- package/package.json +35 -4
- package/src/canonical.js +46 -0
- package/src/cli.js +448 -0
- package/src/errors.js +31 -0
- package/src/evidence.js +133 -0
- package/src/interview.js +362 -0
- package/src/io.js +158 -0
- package/src/media.js +194 -0
- package/src/meta.js +10 -0
- package/src/mock-router.js +321 -0
- package/src/open-url.js +38 -0
- package/src/ordo-api.js +204 -0
- package/src/outcome-tools.js +263 -0
- package/src/poll.js +50 -0
- package/src/prompt.js +123 -0
- package/src/router.js +423 -0
- package/src/token.js +168 -0
- package/src/tools.js +448 -0
- package/src/types.js +218 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ordo contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,150 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ordo-pow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Record proof of work on an Ordo production stage from your terminal.
|
|
4
|
+
|
|
5
|
+
You open a running stage in the Ordo console and click **Contribute proof of work**. The console shows a command:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx ordo-pow@latest opw1.…
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Run it. A short interview asks what you did on the stage, the values you measured, your evidence files and anything unusual. When the stage runs on a machine, it also shows you what the machine captured for each of the stage's outcomes (a photo, a trace, a spectrogram) and asks you to confirm or dispute it. The interviewer is a language model served by the [0G Private Computer](https://pc.0g.ai) Router in private mode, so only providers running in a TEE (trusted execution environment) see the conversation. An Ordo can run on standard routing instead, when no TEE provider serves its model; the CLI then says "standard routing (not a TEE)" in its banner, and nothing in the record is labelled TEE. When you confirm, the record goes to Ordo, a signing link opens in your browser, and once you have signed it the command prints the public proof link.
|
|
12
|
+
|
|
13
|
+
Requires Node.js 22 or newer. No dependencies.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npx ordo-pow@latest <token> [--notes <file>] [--api <url>] [--no-open]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To keep the token out of your shell history, pass it in the environment instead:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
ORDO_POW_TOKEN='opw1.…' npx ordo-pow@latest
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Option | |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `--notes <file>` | A `.txt` or `.md` file of your observations. The interviewer records what it states (measurements, your verdict on each outcome) and asks only about the rest. Its text goes to the model like a typed answer; its hash becomes evidence. |
|
|
30
|
+
| `--api <url>` | Talk to this Ordo API instead of the address in the token |
|
|
31
|
+
| `--no-open` | Print the signing link but do not open a browser |
|
|
32
|
+
| `-h`, `--help` | Help |
|
|
33
|
+
| `-v`, `--version` | Version |
|
|
34
|
+
|
|
35
|
+
During the interview, type your answers. `/show` prints what has been recorded so far, `/notes <file>` reads an observations file as your answer, `/quit` stops without sending anything. Before anything is sent you see the full record and confirm with `y`.
|
|
36
|
+
|
|
37
|
+
When the agent wants to show a file of yours to the model (a photo) or transcribe one (a voice note), it asks first, file by file: `? Show bench.jpg (2.1 MB) to the interviewer model …? [y/N]`. Anything but `y` keeps the file hash-only.
|
|
38
|
+
|
|
39
|
+
If you press Ctrl-C while waiting for your signature, run the same command again: it notices the record was already submitted and goes straight back to waiting. A session can only be submitted once.
|
|
40
|
+
|
|
41
|
+
## What leaves your machine
|
|
42
|
+
|
|
43
|
+
- **Your answers** go to the TEE provider through the 0G Router (private mode), and to Ordo as the transcript, together with the measurements, notes and summary. Ordo seals the full record; the public proof shows commitments and checks, never your raw answers or values.
|
|
44
|
+
- **Evidence files never reach Ordo.** For each file you name, Ordo gets its file name, size, media type and SHA-256. Files up to 50 MB.
|
|
45
|
+
- **A file goes to 0G only with your consent, per file**, and only to the TEE provider:
|
|
46
|
+
- a photo (PNG, JPEG, WebP or GIF, up to 5 MB) is shown to the interviewer model as an image, so it can ask about what it sees. At most 12 MB of images go in one request, and once the model has answered, earlier images are replaced with a placeholder, so requests stay under the Router's 20 MB limit. The record marks the file `analyzedBy: { kind: "vision", model, requestId }`;
|
|
47
|
+
- a voice note (WAV, MP3, M4A, OGG or WebM, up to 12 MB) is transcribed by the session's speech-to-text model (`whisper-large-v3` on mainnet). The transcript goes into your sealed record as a voice note, the audio does not; the call's receipt joins the others with `kind: "transcription"`.
|
|
48
|
+
- Anything else (PDF, video, CSV, logs) stays hash-only.
|
|
49
|
+
- **The machine's media** comes from Ordo, not from you: `review_outcome` downloads what the stage's machine captured, checks it against the hash the machine signed, saves a copy under your temp folder (`ordo-pow-<session>/`) for you to open, and shows it to the model. Your record binds the hashes of the versions you reviewed.
|
|
50
|
+
- **Secrets are never printed.** The token's secret and the session's Router key stay in memory. The Router key is fetched with the token, so it never appears in the command you copy.
|
|
51
|
+
|
|
52
|
+
## The stage's machine
|
|
53
|
+
|
|
54
|
+
When the stage runs on a machine, the session lists its outcomes: what the machine captures (a photo of the part, a pressure trace, an acoustic recording), the metric its on-board agent reads from it, and the target. The interviewer goes through each one:
|
|
55
|
+
|
|
56
|
+
| Tool | What it does |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `review_outcome {key}` | Loads the latest capture: the value, the target, the machine's own 0G reading, and the media as an image (a photo, a video keyframe, an audio spectrogram or a chart), or an excerpt for a CSV or text file. |
|
|
59
|
+
| `confirm_outcome {key, verdict, observed?, note?}` | Records your verdict: `confirm` (it matches what you saw), `dispute` (it does not: what differs, and the value you observed if you give one) or `unsure`. |
|
|
60
|
+
| `ask_machine {question}` | Asks the machine's on-board agent about its run (offered only when the machine has a running body). Its answer is the machine's account, never your statement. At most 30 questions per session. |
|
|
61
|
+
|
|
62
|
+
`finish` is refused until every required outcome the machine captured has been reviewed and confirmed or disputed. A disputed outcome is shown to the validators who choose between proofs of work.
|
|
63
|
+
|
|
64
|
+
## Verifiability
|
|
65
|
+
|
|
66
|
+
Every request to the Router goes out with the session's trust mode in `X-0G-Provider-Trust-Mode`. In private mode, the default, that is `private` (TEE providers only, no fallback to non-TEE providers) with `"verify_tee": true` (the Router checks the provider's TEE signature itself and reports the result). For every Router response the agent keeps a receipt, and all receipts are posted to Ordo with the record:
|
|
67
|
+
|
|
68
|
+
| Receipt field | Source |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `resKey` | `ZG-Res-Key` response header: the chat ID the provider signed. Falls back to the body's `id` when the header is absent. |
|
|
71
|
+
| `requestId` | `x_0g_trace.request_id`, else the `X-Request-ID` header: the Router's id for the request. |
|
|
72
|
+
| `provider` | `x_0g_trace.provider`, else the `X-Provider` header: the on-chain address of the provider that served the answer. |
|
|
73
|
+
| `providerIdentity` | `x_0g_trace.provider_identity`, when the provider reports one. |
|
|
74
|
+
| `teeVerified` | `x_0g_trace.tee_verified`: `true` when the Router verified the provider's TEE signature, `false` when a signature was present but did not verify (the answer is untrusted), `null` when not reported. |
|
|
75
|
+
| `model` | The model that answered. |
|
|
76
|
+
| `requestSha256` | SHA-256 of the exact request bytes sent (canonical JSON: sorted keys, no whitespace). |
|
|
77
|
+
| `responseSha256` | SHA-256 of the exact response bytes received. |
|
|
78
|
+
| `headers` | Every response header starting with `zg-`, `x-0g-` or `0g-`, plus `x-request-id` and `x-provider`. |
|
|
79
|
+
| `trace` | The whole `x_0g_trace` object, including `billing`. |
|
|
80
|
+
|
|
81
|
+
A voice note's transcription goes to the Router's `POST …/v1/audio/transcriptions?verify_tee=true` with a JSON body (`model`, `file_base64`, `filename`, `response_format: "json"`) and the same private-mode header; its receipt has the same fields plus `kind: "transcription"`, and its `requestId` is kept on the voice note.
|
|
82
|
+
|
|
83
|
+
Ordo does not have to trust the Router's `tee_verified` flag. With `resKey` and `provider` it can verify each answer independently, as the 0G docs describe: read the provider's service record (URL and `teeSignerAddress`) from the on-chain serving contract, fetch `GET {provider url}/v1/proxy/signature/{resKey}?model={model}` (returns `{ text, signature }`), and check the EIP-191 signature against `teeSignerAddress`. The 0G compute SDK does the same in `broker.inference.processResponse(provider, chatID)`.
|
|
84
|
+
|
|
85
|
+
Sources:
|
|
86
|
+
- 0G Router, Verifiable Execution: <https://docs.0g.ai/developer-hub/building-on-0g/compute-network/router/features/verifiable-execution>
|
|
87
|
+
- 0G Router, Chat Completions (`x_0g_trace`, tools): <https://docs.0g.ai/developer-hub/building-on-0g/compute-network/router/features/chat-completions>
|
|
88
|
+
- 0G Router, Privacy (trust modes): <https://docs.0g.ai/developer-hub/building-on-0g/compute-network/router/privacy>
|
|
89
|
+
- 0G Router, Errors: <https://docs.0g.ai/developer-hub/building-on-0g/compute-network/router/errors>
|
|
90
|
+
- 0G Router API reference: <https://0gfoundation.github.io/0g-router/>
|
|
91
|
+
|
|
92
|
+
## What the agent sends to Ordo
|
|
93
|
+
|
|
94
|
+
`POST /v1/pow/session/finish` with the record you confirmed:
|
|
95
|
+
|
|
96
|
+
| Field | |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `measurements` | `[{ criterion, value, unit?, note? }]`: the values you gave (never the machine's outcome metrics, which you confirm instead) |
|
|
99
|
+
| `evidence` | `[{ name, sha256, size, mediaType?, analyzedBy? }]`; `analyzedBy` is `{ model, requestId, kind: "vision" \| "transcription" }` when you let 0G see the file |
|
|
100
|
+
| `confirmations` | `[{ outcome, sha256, verdict, observed?, note }]`: your verdict on each outcome, bound to the hash of the version you reviewed |
|
|
101
|
+
| `voiceNotes` | `[{ name, sha256, size, mediaType, transcript, model, requestId, at }]` |
|
|
102
|
+
| `notes`, `summary`, `transcript`, `receipts` | Your notes, the summary you confirmed, the conversation, and the Router receipts |
|
|
103
|
+
|
|
104
|
+
Ordo checks each confirmation against what the machine captured (the outcome exists, the hash is one of its signed versions), seals the record with the machine's media hashes and its answers to `ask_machine`, and the signature you give covers all of it.
|
|
105
|
+
|
|
106
|
+
## Errors from the Router
|
|
107
|
+
|
|
108
|
+
| Status | What you see | Retry |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| 401 | The session's key was rejected; get a new command from the console | no |
|
|
111
|
+
| 402 | The organization's 0G balance is too low; ask your Ordo manager to top it up on pc.0g.ai | no |
|
|
112
|
+
| 429 | Rate limited | once, after `Retry-After` |
|
|
113
|
+
| 502, 503 | Provider failed, or no private (TEE) provider is serving the model right now | once, after a short wait |
|
|
114
|
+
|
|
115
|
+
After a failure you can press Enter to try again or type `/quit`. Nothing you recorded is lost while the command is running.
|
|
116
|
+
|
|
117
|
+
## Exit codes
|
|
118
|
+
|
|
119
|
+
| Code | |
|
|
120
|
+
|---|---|
|
|
121
|
+
| 0 | Signed; the proof link was printed |
|
|
122
|
+
| 1 | Failed: session invalid or expired, Ordo unreachable, not signed within 30 minutes |
|
|
123
|
+
| 2 | Bad arguments or token |
|
|
124
|
+
| 3 | You stopped (`/quit`, or input ended) before submitting |
|
|
125
|
+
| 130 | Interrupted with Ctrl-C |
|
|
126
|
+
|
|
127
|
+
## Environment
|
|
128
|
+
|
|
129
|
+
| Variable | |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `ORDO_POW_TOKEN` | The token, instead of the argument |
|
|
132
|
+
| `ORDO_POW_NO_OPEN=1` | Same as `--no-open` |
|
|
133
|
+
| `ORDO_POW_MOCK_ROUTER=1` | Forces the scripted offline interviewer instead of the Router. For testing: no LLM is called (model `ordo-mock-interviewer`), and its receipts are marked as mock (`provider: null`, `trace.mock: true`), so Ordo will not treat them as TEE receipts. The agent also uses it on its own when the Ordo deployment has no Router key (its session says `router.network: "mock"`). |
|
|
134
|
+
| `ORDO_POW_POLL_INTERVAL_MS` | Status check interval while waiting for the signature (default 3000) |
|
|
135
|
+
| `ORDO_POW_POLL_TIMEOUT_MS` | How long to wait for the signature (default 1800000, 30 minutes) |
|
|
136
|
+
| `NO_COLOR=1` | Plain output |
|
|
137
|
+
| `ORDO_POW_DEBUG=1` | Print stack traces for unexpected errors |
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
pnpm --filter ordo-pow test # node:test: unit tests plus end-to-end runs against a local Ordo API and Router
|
|
143
|
+
pnpm --filter ordo-pow typecheck # JSDoc types checked by tsc, no build step
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The package is plain ESM JavaScript with JSDoc types; what is in `src/` is what runs.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
package/bin/ordo-pow.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Entry point. Kept tiny so an old Node prints a clear message instead of a syntax error.
|
|
3
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
4
|
+
if (major < 22) {
|
|
5
|
+
process.stderr.write(`ordo-pow needs Node.js 22 or newer; this is Node ${process.versions.node}.\n`);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
const { run } = await import("../src/cli.js");
|
|
9
|
+
await run();
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ordo-pow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Record proof of work on an Ordo stage: a short terminal interview through the 0G Private Computer Router (TEE providers only), signed in the Ordo console.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ordo-pow": "bin/ordo-pow.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ordo",
|
|
21
|
+
"proof-of-work",
|
|
22
|
+
"0g",
|
|
23
|
+
"tee",
|
|
24
|
+
"private-computer",
|
|
25
|
+
"manufacturing",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"test": "node --test \"test/**/*.test.js\"",
|
|
30
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
31
|
+
"prepublishOnly": "npm run typecheck && npm test"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.7.5",
|
|
35
|
+
"typescript": "^7.0.2"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/canonical.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Canonical JSON (object keys sorted, no whitespace) and SHA-256 helpers.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Serialize like JSON.stringify, but with object keys sorted at every level so the same
|
|
6
|
+
* value always produces the same bytes. Undefined members and functions are dropped,
|
|
7
|
+
* as JSON.stringify does; non-finite numbers become null.
|
|
8
|
+
* @param {unknown} value
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function canonicalJson(value) {
|
|
12
|
+
const out = serialize(value);
|
|
13
|
+
return out === undefined ? "null" : out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {unknown} value
|
|
18
|
+
* @returns {string | undefined}
|
|
19
|
+
*/
|
|
20
|
+
function serialize(value) {
|
|
21
|
+
if (value === null) return "null";
|
|
22
|
+
if (value === undefined || typeof value === "function" || typeof value === "symbol") return undefined;
|
|
23
|
+
if (typeof value === "number") return Number.isFinite(value) ? JSON.stringify(value) : "null";
|
|
24
|
+
if (typeof value === "bigint") throw new TypeError("canonicalJson: BigInt is not serializable");
|
|
25
|
+
if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
26
|
+
const withToJson = /** @type {{ toJSON?: () => unknown }} */ (value);
|
|
27
|
+
if (typeof withToJson.toJSON === "function") return serialize(withToJson.toJSON());
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return `[${value.map((item) => serialize(item) ?? "null").join(",")}]`;
|
|
30
|
+
}
|
|
31
|
+
const record = /** @type {Record<string, unknown>} */ (value);
|
|
32
|
+
const members = [];
|
|
33
|
+
for (const key of Object.keys(record).sort()) {
|
|
34
|
+
const member = serialize(record[key]);
|
|
35
|
+
if (member !== undefined) members.push(`${JSON.stringify(key)}:${member}`);
|
|
36
|
+
}
|
|
37
|
+
return `{${members.join(",")}}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hex SHA-256 of a string (UTF-8) or bytes.
|
|
42
|
+
* @param {string | Uint8Array} data
|
|
43
|
+
*/
|
|
44
|
+
export function sha256Hex(data) {
|
|
45
|
+
return createHash("sha256").update(data).digest("hex");
|
|
46
|
+
}
|