@oneie/sdk 0.14.14 → 0.15.0
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/dist/.build-fingerprint +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/generated/fn-map.d.ts +2 -2
- package/dist/generated/fn-map.d.ts.map +1 -1
- package/dist/generated/fn-map.js +266 -2
- package/dist/generated/fn-map.js.map +1 -1
- package/dist/generated/schemas.d.ts +1 -1
- package/dist/generated/types.d.ts +3 -0
- package/dist/generated/types.d.ts.map +1 -1
- package/dist/receivers.d.ts +222 -3
- package/dist/receivers.d.ts.map +1 -1
- package/dist/receivers.js +473 -2
- package/dist/receivers.js.map +1 -1
- package/dist/tasks-bulk.d.ts +90 -0
- package/dist/tasks-bulk.d.ts.map +1 -0
- package/dist/tasks-bulk.js +376 -0
- package/dist/tasks-bulk.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/work-contract-grammar.json +9 -0
- package/package.json +1 -1
package/dist/receivers.js
CHANGED
|
@@ -159,6 +159,12 @@ export const RECEIVERS = {
|
|
|
159
159
|
summary: "Agency mints a scoped invite; on accept the invitee owns a new workspace parented under the agency",
|
|
160
160
|
request: z.object({
|
|
161
161
|
slug: z.string(),
|
|
162
|
+
// The invitee's slug. Prefer this over `slug`: a trusted service caller
|
|
163
|
+
// nominates its OWN workspace with `slug` (the ask route reads payload.slug
|
|
164
|
+
// as the caller identity), so naming the invitee there self-parents the
|
|
165
|
+
// invite and mints credits against no sponsor. Refused server-side when the
|
|
166
|
+
// resolved child equals the sponsor.
|
|
167
|
+
child: z.string().optional(),
|
|
162
168
|
name: z.string().optional(),
|
|
163
169
|
email: z.string().email().optional(),
|
|
164
170
|
plan: z.enum(["free", "pro", "studio", "agency", "enterprise"]).optional(),
|
|
@@ -170,7 +176,7 @@ export const RECEIVERS = {
|
|
|
170
176
|
}),
|
|
171
177
|
response: z.object({ token: z.string(), id: z.string(), child: z.string(), expiresAt: z.number() }),
|
|
172
178
|
effect: "ask", auth: "manage_clients", reversible: false,
|
|
173
|
-
examples: [{ slug: "beta", email: "owner@beta.com", plan: "studio", credits: 50000, markup: 20 }],
|
|
179
|
+
examples: [{ slug: "one", child: "beta", email: "owner@beta.com", plan: "studio", credits: 50000, markup: 20 }],
|
|
174
180
|
}),
|
|
175
181
|
"world:accept-client-invite": receiver({
|
|
176
182
|
receiver: "world:accept-client-invite",
|
|
@@ -239,6 +245,61 @@ export const RECEIVERS = {
|
|
|
239
245
|
request: z.object({ gid: z.string(), name: z.string().optional(), tags: z.array(z.string()).optional(), meta: z.unknown().optional() }),
|
|
240
246
|
response: ok, effect: "ask", auth: "manage_groups", roleAction: "update_group",
|
|
241
247
|
}),
|
|
248
|
+
/**
|
|
249
|
+
* The only non-HTTP door to a workspace's brand. Tokens previously reached R2
|
|
250
|
+
* through `pages/api/{branding,settings,onboarding}.ts` only, so a workflow
|
|
251
|
+
* step, an agent, the CLI and MCP could not persist a theme at all.
|
|
252
|
+
*
|
|
253
|
+
* `auth: "manage_workspace"` is chosen because it is already in
|
|
254
|
+
* bind-receiver.ts's AUTH_POLICY mapping to the ENFORCED `tenant` class — an
|
|
255
|
+
* invented label would fall through `AUTH_POLICY[label] ?? 'authenticated'`
|
|
256
|
+
* to the WARN-only class and ship a workspace write with no enforced floor.
|
|
257
|
+
*
|
|
258
|
+
* `cost: "free"` is a declaration, not an omission: an R2 read+put costs no
|
|
259
|
+
* provider dollars, and per text/credit-allocation.md an UNDECLARED cost must
|
|
260
|
+
* not read as free. `reversible: false` is honest — the response reports which
|
|
261
|
+
* keys were written but not their prior values, so the caller cannot restore
|
|
262
|
+
* them from it. Handler: one.ie/web/src/lib/resolvers/brand.ts.
|
|
263
|
+
*
|
|
264
|
+
* The write is held to WCAG AA by the SAME `auditBrandContrast` the brand
|
|
265
|
+
* editor route calls, so a theme written by a workflow step meets the bar a
|
|
266
|
+
* human-written one does.
|
|
267
|
+
*/
|
|
268
|
+
"world:set-brand-tokens": receiver({
|
|
269
|
+
receiver: "world:set-brand-tokens",
|
|
270
|
+
summary: "Write validated colour tokens to a workspace's brand (site.md)",
|
|
271
|
+
request: z.object({
|
|
272
|
+
/** The workspace being rebranded. The ONLY workspace field: authority is
|
|
273
|
+
* decided against this value and it is the only one written to. */
|
|
274
|
+
workspace: z.string(),
|
|
275
|
+
/** Frontmatter keys to set. The six light tokens (primary, secondary,
|
|
276
|
+
* tertiary, background, foreground, font) and their `dark-` overrides;
|
|
277
|
+
* any other key is ignored, never written. `"unset"` on a `dark-` key
|
|
278
|
+
* deletes the override, re-linking it to the light value. Values are
|
|
279
|
+
* syntax-gated with the same HEX_OR_HSL regex `parseSite` reads with, then
|
|
280
|
+
* checked readable with `parseColor`, then held to WCAG AA by the same
|
|
281
|
+
* `auditBrandContrast` the brand editor route calls. Any invalid value —
|
|
282
|
+
* or any AA failure on a pair THIS write touches — refuses the WHOLE
|
|
283
|
+
* write; pre-existing failures come back as `warnings`. */
|
|
284
|
+
tokens: z.record(z.string(), z.string()),
|
|
285
|
+
}),
|
|
286
|
+
response: z.object({
|
|
287
|
+
ok: z.boolean(),
|
|
288
|
+
workspace: z.string().optional(),
|
|
289
|
+
written: z.array(z.string()).optional(),
|
|
290
|
+
bytes: z.number().optional(),
|
|
291
|
+
ignored: z.array(z.string()).optional(),
|
|
292
|
+
error: z.string().optional(),
|
|
293
|
+
invalid: z.array(z.object({ field: z.string(), value: z.string(), reason: z.string() })).optional(),
|
|
294
|
+
/** WCAG AA pairs THIS write touches that fail — the write is refused. */
|
|
295
|
+
failures: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
296
|
+
/** Pairs it leaves alone that already fail — reported, never blocking. */
|
|
297
|
+
warnings: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
298
|
+
}),
|
|
299
|
+
effect: "ask", auth: "manage_workspace", roleAction: "update_group",
|
|
300
|
+
cost: "free", reversible: false, idempotent: true, settles: "none",
|
|
301
|
+
examples: [{ workspace: "acme", tokens: { primary: "#1a73e8", "dark-primary": "#8ab4f8" } }],
|
|
302
|
+
}),
|
|
242
303
|
"world:remove-group": receiver({
|
|
243
304
|
receiver: "world:remove-group",
|
|
244
305
|
summary: "Delete a group",
|
|
@@ -1045,6 +1106,39 @@ export const RECEIVERS = {
|
|
|
1045
1106
|
}),
|
|
1046
1107
|
effect: "ask", idempotent: true, reversible: true,
|
|
1047
1108
|
}),
|
|
1109
|
+
"factory:recon": receiver({
|
|
1110
|
+
receiver: "factory:recon",
|
|
1111
|
+
summary: "What does this sentence touch — the paths, the route and the verdict `.claude/scripts/do-triage.sh` returns for the same text, answered where a Worker can ask (a Worker cannot exec a shell script, which is the only reason the recon card at factory flow.ts:480 was a gap). Runs UPSTREAM of factory:size: prose in, repo paths out, and the sizer's own contract unchanged. No model call. It ALWAYS answers with a verdict and never refuses — it runs as a workflow tool step (one.ie/ai/workflows/eng-intake.tql:33), where a failed step poisons every step downstream of it; factory:size's `unsized` stays the one refusal on this lane",
|
|
1112
|
+
request: z.object({
|
|
1113
|
+
// The captured sentence. do-triage reads what the text LITERALLY names —
|
|
1114
|
+
// there is no model here, so a sentence that names nothing answers `idea`.
|
|
1115
|
+
intent: z.string().max(2000),
|
|
1116
|
+
// Paths a caller already knows. Accepted so a workflow step can pass the
|
|
1117
|
+
// trigger through whole; recon derives its own from `intent` and does not
|
|
1118
|
+
// widen the answer with these.
|
|
1119
|
+
paths: z.array(z.string()).max(2000).optional(),
|
|
1120
|
+
// The trigger's tags, carried not read. Declared for one measured reason:
|
|
1121
|
+
// zod `.object()` STRIPS unknown keys, so an undeclared field arriving from
|
|
1122
|
+
// a workflow step is a shape error rather than a no-op. It decides nothing —
|
|
1123
|
+
// same rule as factory:size's `workspace`: a body field that decided nothing
|
|
1124
|
+
// cannot be spoofed into deciding something.
|
|
1125
|
+
tags: z.array(z.string()).max(64).optional(),
|
|
1126
|
+
}),
|
|
1127
|
+
response: z.object({
|
|
1128
|
+
// Always true. There is no `ok:false` branch in this receiver.
|
|
1129
|
+
ok: z.boolean(),
|
|
1130
|
+
verdict: z.enum(["sizeable", "idea", "dupe", "split"]).optional(),
|
|
1131
|
+
// Repo-relative, existing, in the order the sentence names them. This is the
|
|
1132
|
+
// array that goes to factory:size unchanged.
|
|
1133
|
+
paths: z.array(z.string()).optional(),
|
|
1134
|
+
route: z.string().nullable().optional(),
|
|
1135
|
+
// Every path-SHAPED token the sentence named, before the existence filter.
|
|
1136
|
+
// The edge can confirm fewer paths than the shell can (it has a page glob,
|
|
1137
|
+
// not a repo), and this is that difference made legible rather than hidden.
|
|
1138
|
+
named: z.array(z.string()).optional(),
|
|
1139
|
+
}),
|
|
1140
|
+
effect: "ask", idempotent: true, reversible: true,
|
|
1141
|
+
}),
|
|
1048
1142
|
"factory:attempt": receiver({
|
|
1049
1143
|
receiver: "factory:attempt",
|
|
1050
1144
|
summary: "Open an attempt contained by its task, or close it with the production edge to what it produced plus the rubric. A do-cycle IS an attempt (factory-plan.md §3.1) — do:cycle-close arms as a caller of the close phase, never as a second writer",
|
|
@@ -1084,6 +1178,73 @@ export const RECEIVERS = {
|
|
|
1084
1178
|
}),
|
|
1085
1179
|
effect: "ask", idempotent: true,
|
|
1086
1180
|
}),
|
|
1181
|
+
"deploy:event": receiver({
|
|
1182
|
+
receiver: "deploy:event",
|
|
1183
|
+
summary: "One deploy gate lands on the same run/event stream the canvas already reads — the ten-gate pipeline (tree·credentials·typecheck·tests·build·smoke·approval·migrations·ship·health) that until now touched the substrate NOWHERE, leaving a committed JSON file as a deploy's only trace. Carries the metrics that file has no shape for: whether the suite was REUSED or re-run, whether a gate passed under a waiver, and which services actually shipped. An unknown stage, status or verdict is REFUSED, never recorded as a default frame",
|
|
1184
|
+
request: z.object({
|
|
1185
|
+
// The deploy's own run id — deploy.sh already mints one for its log name.
|
|
1186
|
+
// It IS half the run key, which is why a retried gate appends to its own
|
|
1187
|
+
// run instead of minting a second one. The sha cannot serve: the same sha
|
|
1188
|
+
// ships to the same target repeatedly (2026-09-06: af3ba5919 gates-only,
|
|
1189
|
+
// red, green, then shipped), and keying on it would interleave them.
|
|
1190
|
+
run: z.string(),
|
|
1191
|
+
// Where it ships. The other half of the key, so "did pay go out" is
|
|
1192
|
+
// answerable without disambiguating five services by timestamp.
|
|
1193
|
+
target: z.string(),
|
|
1194
|
+
// Must equal DEPLOY_SPINE_STEPS (one.ie/web/src/lib/deploy/event.ts).
|
|
1195
|
+
// Retyped because packages/sdk cannot import from one.ie/web; pinned by
|
|
1196
|
+
// tests/unit/deploy/event.test.ts so the two cannot drift.
|
|
1197
|
+
stage: z.enum([
|
|
1198
|
+
"tree", "credentials", "typecheck", "tests", "build",
|
|
1199
|
+
"smoke", "approval", "migrations", "ship", "health",
|
|
1200
|
+
]).optional(),
|
|
1201
|
+
// There is deliberately no "skip": a gate that did not run must be ABSENT.
|
|
1202
|
+
// deploy.sh already reports four gate states (pass|fail|unrun|n/a) and an
|
|
1203
|
+
// unrun gate is never a pass — a "skip" frame would render like one.
|
|
1204
|
+
status: z.enum(["start", "ok", "fail"]).optional(),
|
|
1205
|
+
// Present only on the final call, and it is what CLOSES the run. Separate
|
|
1206
|
+
// from the stages because --gates-only is a complete, successful run that
|
|
1207
|
+
// legitimately never reaches `ship` or `health`; closing on the last stage
|
|
1208
|
+
// would leave every one of those runs "open" forever.
|
|
1209
|
+
verdict: z.enum(["green", "red"]).optional(),
|
|
1210
|
+
sha: z.string().optional(),
|
|
1211
|
+
// Which door ran it: ./deploy, release.sh ship, land --pr --deploy.
|
|
1212
|
+
door: z.string().optional(),
|
|
1213
|
+
// Board workspace. TAGS ONLY inside the resolver — effectiveWorkspace
|
|
1214
|
+
// reads `workspace` and never `slug`. The same caller-nomination note as
|
|
1215
|
+
// factory:event's `slug` field applies verbatim for a service caller.
|
|
1216
|
+
slug: z.string().optional(),
|
|
1217
|
+
// "dev" flags the projected run is_test so dev.one.ie never pollutes the
|
|
1218
|
+
// production list. --dry-run does not reach here at all: the emitter is
|
|
1219
|
+
// not called on that path, because a run row for a deploy that did not
|
|
1220
|
+
// happen is the theater /deploy already refuses to render.
|
|
1221
|
+
env: z.string().optional(),
|
|
1222
|
+
reason: z.string().optional(),
|
|
1223
|
+
// This gate's own wall clock. Written to workflow_run_event.latency_ms —
|
|
1224
|
+
// the column factory:event binds NULL because a factory stage has no
|
|
1225
|
+
// measured duration. It is the number this whole receiver exists for.
|
|
1226
|
+
wallMs: z.number().optional(),
|
|
1227
|
+
// reused / waived / services — see DeployEventPayload.detail.
|
|
1228
|
+
detail: z.record(z.string(), z.unknown()).optional(),
|
|
1229
|
+
workspace: z.string().optional(),
|
|
1230
|
+
}),
|
|
1231
|
+
response: z.object({
|
|
1232
|
+
ok: z.boolean(),
|
|
1233
|
+
runId: z.string().optional(),
|
|
1234
|
+
stage: z.string().optional(),
|
|
1235
|
+
status: z.string().optional(),
|
|
1236
|
+
verdict: z.string().optional(),
|
|
1237
|
+
events: z.number().optional(),
|
|
1238
|
+
slug: z.string().optional(),
|
|
1239
|
+
error: z.string().optional(),
|
|
1240
|
+
}),
|
|
1241
|
+
// Same floor and same reasoning as factory:event: authClassFor(undefined)
|
|
1242
|
+
// returns 'open', and the 'authenticated' floor tests exactly the predicate
|
|
1243
|
+
// the handler's own guard uses, one layer earlier. Declaring it makes the
|
|
1244
|
+
// fail-closed intent survive a refactor of the handler.
|
|
1245
|
+
auth: "required",
|
|
1246
|
+
effect: "ask", idempotent: true,
|
|
1247
|
+
}),
|
|
1087
1248
|
"factory:event": receiver({
|
|
1088
1249
|
receiver: "factory:event",
|
|
1089
1250
|
summary: "One factory-executor stage lands on the same run/event stream the canvas already reads — the six-stage pipeline (ready·claim·build·review·prove·close) that until now touched the substrate only at claim and close, so every surface could show a job before and after but never during. An unknown stage or status is REFUSED, never recorded as a default frame",
|
|
@@ -1160,6 +1321,114 @@ export const RECEIVERS = {
|
|
|
1160
1321
|
// a dedupe. The RUN row dedupes (INSERT OR IGNORE); the events do not.
|
|
1161
1322
|
effect: "ask", idempotent: false,
|
|
1162
1323
|
}),
|
|
1324
|
+
"story:chain": receiver({
|
|
1325
|
+
receiver: "story:chain",
|
|
1326
|
+
summary: "The stories, counted: sets of two or more signals sharing one `origin`, read from the D1 lineage ledger (migrations/0244_story_link.sql). A story is a COUNT over a shared origin, not an entity \u2014 zero new types, zero new dimensions. The number moves as a side effect of signals that already flow; nobody types a row. `count` is the whole qualifying population, `stories` a page of at most 50 ordered by members desc \u2014 a capped count would plateau exactly when the substrate got busy. Zero is a real answer, never an error",
|
|
1327
|
+
request: z.object({
|
|
1328
|
+
// Where "a story is two or more" lives. The floor is the caller's, so 1
|
|
1329
|
+
// legitimately means "every origin"; the default is the contract's 2.
|
|
1330
|
+
minMembers: z.number().int().min(1).max(1_000_000).optional(),
|
|
1331
|
+
// Narrow to one story. The same floor still applies: an origin holding a
|
|
1332
|
+
// single signal answers count:0, because one signal is not yet a story.
|
|
1333
|
+
origin: z.string().max(128).optional(),
|
|
1334
|
+
// Page size only. Capped at 50 by the reader; it can never widen the scan.
|
|
1335
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
1336
|
+
}),
|
|
1337
|
+
response: z.object({
|
|
1338
|
+
ok: z.boolean(),
|
|
1339
|
+
count: z.number(),
|
|
1340
|
+
minMembers: z.number().optional(),
|
|
1341
|
+
stories: z.array(z.object({
|
|
1342
|
+
origin: z.string(),
|
|
1343
|
+
members: z.number(),
|
|
1344
|
+
// NULLABLE on purpose: `ts` is nullable in 0244, so MIN/MAX over a row
|
|
1345
|
+
// that carried none is NULL. Typing these bare numbers would be a lie
|
|
1346
|
+
// the response validator eventually catches in prod.
|
|
1347
|
+
first: z.number().nullable().optional(),
|
|
1348
|
+
last: z.number().nullable().optional(),
|
|
1349
|
+
})).optional(),
|
|
1350
|
+
}),
|
|
1351
|
+
// PUBLIC, and the reason is the ledger's shape, not an oversight. 0244 stores
|
|
1352
|
+
// signal_id, origin, receiver and ts \u2014 "no actor, no payload and no ip", in
|
|
1353
|
+
// its own words \u2014 so there is nothing here to scope to a tenant and nothing
|
|
1354
|
+
// to leak by reading it. The alternative is worse than permissive: the binder
|
|
1355
|
+
// attests on `staff || ownerSlug` (bind-receiver.ts applyAuth), and a service
|
|
1356
|
+
// caller nominates ownerSlug only via a `slug`/`workspace` body field, which
|
|
1357
|
+
// this contract has no reason to carry \u2014 so an `authenticated` label would
|
|
1358
|
+
// refuse the very call the promise's Proof makes.
|
|
1359
|
+
auth: "public",
|
|
1360
|
+
// A pure read. Same inputs, same answer, until a signal moves the number.
|
|
1361
|
+
effect: "ask", idempotent: true, reversible: true,
|
|
1362
|
+
}),
|
|
1363
|
+
// story:event — the funnel's doors. text/story-framework.md § "The story is
|
|
1364
|
+
// alive" names nine steps and schema/story.tql block 4 declares the EVENT
|
|
1365
|
+
// CONVENTION they are counted by; until this receiver existed nothing in the
|
|
1366
|
+
// estate WROTE one, so every block-4 fun answered 0 and block 5's story_death
|
|
1367
|
+
// answered "queen" for every story that had ever been told
|
|
1368
|
+
// (text/story-tracking-plan.md § Gaps 4). This is the write half.
|
|
1369
|
+
//
|
|
1370
|
+
// ONE signal per call, under the story's origin, carrying the block-4 payload
|
|
1371
|
+
// pairs: {"origin":"<o>","event":"story:<kind>"} plus "parent" on a retell and
|
|
1372
|
+
// an `amount` attribute on a convert. The graph write and the trail marks are
|
|
1373
|
+
// deferred to waitUntil — the brain takes the write, never the request path
|
|
1374
|
+
// (root CLAUDE.md § The brain and the edge).
|
|
1375
|
+
//
|
|
1376
|
+
// AUTH IS PER-KIND, which no single label can express, so the label is the
|
|
1377
|
+
// widest kind and the resolver holds the rest: `told`, `convert` and `retell`
|
|
1378
|
+
// refuse an unattested caller (they name a teller, move money, or claim
|
|
1379
|
+
// lineage), while `framed`/`view`/`complete`/`share` are open — a reader of a
|
|
1380
|
+
// published story has no session and a view nobody can send is not a funnel.
|
|
1381
|
+
// The identity is ALWAYS ctx, never the body (memory: resolver-body-actorid).
|
|
1382
|
+
"story:event": receiver({
|
|
1383
|
+
receiver: "story:event",
|
|
1384
|
+
summary: "One step of a story's life, written as a signal under its origin: told · framed · view · complete · share · convert · retell. The payload carries the block-4 event convention (schema/story.tql), so story_views / story_conversions / story_revenue / story_retells / story_teller start answering from real traffic instead of 0. A convert also marks the two craft trails — tag:<board>:story → tag:<board>:frame:<x> and → tag:<board>:medium:<x> — and a retell marks teller → reteller, the referral edge. told/convert/retell need an attested caller; view/complete/share may be anonymous and are deduped per (origin, viewer, event) for an hour",
|
|
1385
|
+
request: z.object({
|
|
1386
|
+
// The story. Minted by the door (C3) and echoed on every response, so a
|
|
1387
|
+
// caller always has one to quote; never derived from a task id.
|
|
1388
|
+
origin: z.string().min(1).max(128),
|
|
1389
|
+
// The nine-step funnel's kinds, minus the four a rung already writes
|
|
1390
|
+
// (promised/building/settled are the promise's own states).
|
|
1391
|
+
event: z.enum(["told", "framed", "view", "complete", "share", "convert", "retell"]),
|
|
1392
|
+
// Revenue on a convert. 0 is a real answer — a sign-up converts and moves
|
|
1393
|
+
// no money — so it is not conflated with absent.
|
|
1394
|
+
amount: z.number().nonnegative().max(1_000_000).optional(),
|
|
1395
|
+
// REQUIRED on a retell: the origin of the story this one was told from.
|
|
1396
|
+
// A retell without it is lineage that names no parent, which is what
|
|
1397
|
+
// story_children reads, so it is refused rather than written half-formed.
|
|
1398
|
+
parent: z.string().max(128).optional(),
|
|
1399
|
+
// What converted — the framework and the medium. Named on a convert, they
|
|
1400
|
+
// become the two marks; absent, the convert still counts and marks nothing.
|
|
1401
|
+
frame: z.string().max(64).optional(),
|
|
1402
|
+
medium: z.string().max(64).optional(),
|
|
1403
|
+
// TENANCY: honoured only when it equals the attested caller's own slug.
|
|
1404
|
+
// A caller that could name another board could poison another tenant's
|
|
1405
|
+
// routing weights (resolvers/subscriptions.ts:399-403).
|
|
1406
|
+
board: z.string().max(64).optional(),
|
|
1407
|
+
// Whether the medium played to the end — the one thing story_completes
|
|
1408
|
+
// reads `has success true` for. Defaults true.
|
|
1409
|
+
success: z.boolean().optional(),
|
|
1410
|
+
}),
|
|
1411
|
+
response: z.object({
|
|
1412
|
+
ok: z.boolean(),
|
|
1413
|
+
origin: z.string().optional(),
|
|
1414
|
+
// The full pair as written, e.g. "story:view" — so a caller can grep the
|
|
1415
|
+
// same string schema/story.tql matches on.
|
|
1416
|
+
event: z.string().optional(),
|
|
1417
|
+
// TRUE means the window already held this (origin, viewer, event) and no
|
|
1418
|
+
// second signal was written. A real answer, never an error.
|
|
1419
|
+
deduped: z.boolean().optional(),
|
|
1420
|
+
// The trails this call will move, as "<source>→<target>". Empty is honest:
|
|
1421
|
+
// a convert naming no frame and no medium marks nothing.
|
|
1422
|
+
marked: z.array(z.string()).optional(),
|
|
1423
|
+
error: z.string().optional(),
|
|
1424
|
+
}),
|
|
1425
|
+
// The widest kind. See the note above: the narrower kinds are gated in the
|
|
1426
|
+
// resolver on ctx, because one label cannot say "open to read, closed to tell".
|
|
1427
|
+
auth: "public",
|
|
1428
|
+
// It writes. Not idempotent: two views an hour apart are two views, and the
|
|
1429
|
+
// dedupe window is a window, not a key.
|
|
1430
|
+
effect: "signal", cost: "free", reversible: false, idempotent: false,
|
|
1431
|
+
}),
|
|
1163
1432
|
"do:halt": receiver({
|
|
1164
1433
|
receiver: "do:halt",
|
|
1165
1434
|
summary: "Stop a factory run, or release the stop. Sets a halt latch on the plan thing that `factory:attempt` phase \"open\" refuses against, so no new attempt can be claimed; with `attempt`, also closes that in-flight attempt as dissolved. It does NOT kill an OS process — a cycle already executing stops at its next substrate write",
|
|
@@ -1504,6 +1773,26 @@ export const RECEIVERS = {
|
|
|
1504
1773
|
slug: z.string().optional(),
|
|
1505
1774
|
body: z.string().optional(),
|
|
1506
1775
|
workspace: z.string().optional(),
|
|
1776
|
+
// WHICH CONVERSATION THIS CAME FROM. A comment and a chat message already
|
|
1777
|
+
// live in the same D1 message store (`getOrCreateThread`, keyed
|
|
1778
|
+
// `task:<tid>`); what was missing was a field naming the OTHER thread, so
|
|
1779
|
+
// a discussion in the inbox and the row it is about could not be joined.
|
|
1780
|
+
//
|
|
1781
|
+
// It must be DECLARED to exist: `validateReceiver` returns the parsed
|
|
1782
|
+
// object and a zod schema STRIPS undeclared keys, so an undeclared field
|
|
1783
|
+
// never reaches the resolver. That is the same mechanism behind this
|
|
1784
|
+
// door's older surprise — an unknown key degrades the call to a read that
|
|
1785
|
+
// still answers ok:true.
|
|
1786
|
+
threadId: z.string().optional(),
|
|
1787
|
+
// WHO WROTE IT. Declared because a zod schema STRIPS an undeclared key
|
|
1788
|
+
// before the resolver ever sees it — the exact mechanism that left
|
|
1789
|
+
// `thread:append`'s already-implemented agent branch unreachable for every
|
|
1790
|
+
// gateway caller. `authorKind` stays UNDECLARED for the same reason it
|
|
1791
|
+
// does there: its value is a TRUST LABEL, and a caller naming its own is
|
|
1792
|
+
// the estate's most-repeated defect. The resolver derives the label and
|
|
1793
|
+
// honours this field only for an ATTESTED platform-staff caller; a bare
|
|
1794
|
+
// agent key already speaks as itself from ctx and needs no field.
|
|
1795
|
+
author: z.string().optional(),
|
|
1507
1796
|
}).refine((v) => !!v.tid || !!v.slug, {
|
|
1508
1797
|
message: "address the task by tid or slug",
|
|
1509
1798
|
path: ["tid"],
|
|
@@ -2008,10 +2297,12 @@ export const RECEIVERS = {
|
|
|
2008
2297
|
request: z.object({
|
|
2009
2298
|
limit: z.number().optional(), since: z.number().optional(),
|
|
2010
2299
|
from: z.number().optional(), to: z.number().optional(),
|
|
2300
|
+
intent: z.string().optional(),
|
|
2011
2301
|
}),
|
|
2012
2302
|
response: z.array(z.object({
|
|
2013
2303
|
id: z.string(), from: z.string(), to: z.string(), skill: z.string().optional(),
|
|
2014
2304
|
outcome: z.enum(["success", "failure", "timeout"]), revenue: z.number(), ts: z.number(),
|
|
2305
|
+
intent: z.string().optional(),
|
|
2015
2306
|
})),
|
|
2016
2307
|
effect: "ask", cost: "free", idempotent: true,
|
|
2017
2308
|
}),
|
|
@@ -2126,6 +2417,78 @@ export const RECEIVERS = {
|
|
|
2126
2417
|
effect: "ask", cost: "free", idempotent: true,
|
|
2127
2418
|
examples: [{ feature: "premium" }],
|
|
2128
2419
|
}),
|
|
2420
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
2421
|
+
// id: — one door for every address in the estate. `id:resolve` is a pure
|
|
2422
|
+
// projection over `one.ie/web/src/data/id-inventory.json`, the artifact
|
|
2423
|
+
// `.claude/scripts/id-inventory.mjs` writes and its `--check` regenerates in
|
|
2424
|
+
// memory to fail on drift. NOTHING HERE IS HAND-DECLARED: kind, provenance,
|
|
2425
|
+
// count, id_prefix, summary and source are each read off that file, so this
|
|
2426
|
+
// door cannot claim a population the generator never counted.
|
|
2427
|
+
//
|
|
2428
|
+
// Seven kinds — signal · skill · workflow · component · doc · function ·
|
|
2429
|
+
// thing. `thing` is the LOCKED dimension-3 word: a ref whose prefix is
|
|
2430
|
+
// `object:` is REFUSED, never silently rewritten, because a door that
|
|
2431
|
+
// rewrites teaches callers a name the substrate does not have. Functions
|
|
2432
|
+
// address as `fn:` (the estate's existing address — `fn:run`'s FN_MAP),
|
|
2433
|
+
// never `fun:`.
|
|
2434
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
2435
|
+
"id:resolve": receiver({
|
|
2436
|
+
receiver: "id:resolve",
|
|
2437
|
+
summary: "Resolve one address to its metadata card — seven generated kinds behind one door, every field projected from the generated id inventory. `object:` is refused in favour of `thing`; a ref the inventory does not hold answers `unknown_ref` rather than a guess",
|
|
2438
|
+
request: z.object({
|
|
2439
|
+
// The grammar `TID_RE` already admits (resolvers/factory.ts:32): MINTED
|
|
2440
|
+
// `<type>:<24-hex>` or DERIVED `<type>:<scope>[:<n>][/<part>]`. A bare
|
|
2441
|
+
// receiver name is its own address — `factory:size` resolves as a signal,
|
|
2442
|
+
// not as a `factory` scope, because an exact registry name is matched
|
|
2443
|
+
// before any prefix is read (21 receiver names would collide otherwise).
|
|
2444
|
+
ref: z.string().min(1).max(128),
|
|
2445
|
+
}),
|
|
2446
|
+
// `factory:size`'s idiom deliberately: one flat schema, `ok: z.boolean()`,
|
|
2447
|
+
// everything else optional — never a discriminated union. A refusal is the
|
|
2448
|
+
// same shape as an answer, so a caller reads `ok` and nothing else.
|
|
2449
|
+
response: z.object({
|
|
2450
|
+
ok: z.boolean(),
|
|
2451
|
+
ref: z.string().optional(),
|
|
2452
|
+
kind: z.string().optional(),
|
|
2453
|
+
// Lexical, per trace.ts:26-31 — the last colon segment decides. Never
|
|
2454
|
+
// temporal: `attempt:<hex>:<n>` is DERIVED because its last segment is an
|
|
2455
|
+
// ordinal, which is the same answer `traceIdMintedAt` already gives.
|
|
2456
|
+
lane: z.enum(["minted", "derived"]).optional(),
|
|
2457
|
+
name: z.string().optional(),
|
|
2458
|
+
title: z.string().optional(),
|
|
2459
|
+
summary: z.string().optional(),
|
|
2460
|
+
source: z.string().optional(),
|
|
2461
|
+
// BARE, never composed — the inventory stores `fn`, and `fn:<name>` is
|
|
2462
|
+
// built at read time. null for `signal`: a receiver's name IS its address.
|
|
2463
|
+
id_prefix: z.string().nullable().optional(),
|
|
2464
|
+
// Read off the kind record, not asserted here. `generated` for the five
|
|
2465
|
+
// counted kinds; `graph` is what `workflow` and `thing` carry, and they
|
|
2466
|
+
// hold no entries, so they answer `unknown_ref` until the graph is walked.
|
|
2467
|
+
provenance: z.string().optional(),
|
|
2468
|
+
// The kind's population, or null where the inventory refused to count.
|
|
2469
|
+
// null is UNRUN, never zero.
|
|
2470
|
+
count: z.number().nullable().optional(),
|
|
2471
|
+
// The commit the inventory was generated at — the answer's receipt.
|
|
2472
|
+
generated_at: z.string().optional(),
|
|
2473
|
+
error: z.string().optional(),
|
|
2474
|
+
}),
|
|
2475
|
+
// `auth: "authenticated"` — C20, the auditor's second finding. This door has
|
|
2476
|
+
// no `auth:` field until now, and an ABSENT label is the one case
|
|
2477
|
+
// `authClassFor` answers 'open' for (bind-receiver.ts:119), while the doc
|
|
2478
|
+
// five lines above it says an unlisted label falls to 'authenticated'. So
|
|
2479
|
+
// the declaration and the default disagreed, and this receiver was on the
|
|
2480
|
+
// wrong side of it: what it projects IS the web's internals — every id,
|
|
2481
|
+
// summary, source path and population count in the estate, from one
|
|
2482
|
+
// uncredentialed POST. 320 of 335 receivers share the absent label; the
|
|
2483
|
+
// default is NOT inverted this cycle (that needs the census bind-receiver.ts
|
|
2484
|
+
// now logs), so this one says so explicitly instead of waiting for it.
|
|
2485
|
+
//
|
|
2486
|
+
// Costs the contract nothing: text/story.md:24's accept sends
|
|
2487
|
+
// GATEWAY_API_KEY, which `isVerifiedServiceCaller` attests, so the promise's
|
|
2488
|
+
// own check passes unchanged.
|
|
2489
|
+
effect: "ask", auth: "authenticated", cost: "free", idempotent: true,
|
|
2490
|
+
examples: [{ ref: "factory:size" }, { ref: "fn:uncovered" }],
|
|
2491
|
+
}),
|
|
2129
2492
|
"plugins:list": receiver({
|
|
2130
2493
|
receiver: "plugins:list",
|
|
2131
2494
|
summary: "List all available ONE plugins with their feature key, price, and description",
|
|
@@ -2294,6 +2657,16 @@ export const RECEIVERS = {
|
|
|
2294
2657
|
content: z.string(),
|
|
2295
2658
|
ts: z.number().optional(),
|
|
2296
2659
|
tags: z.array(z.string()).optional(),
|
|
2660
|
+
// The resolver implements this branch (messaging.ts) — an agent mirroring
|
|
2661
|
+
// its own reply back into the inbox writes role "assistant", which the
|
|
2662
|
+
// resolver turns into authorKind "agent". Undeclared, zod stripped it
|
|
2663
|
+
// before the resolver ever saw it, so the branch was unreachable for
|
|
2664
|
+
// EVERY gateway caller and every agent post landed as role "user" /
|
|
2665
|
+
// authorKind "customer". `authorKind` stays UNDECLARED deliberately: its
|
|
2666
|
+
// only reachable value is "staff", which is a caller naming its own
|
|
2667
|
+
// author (resolver-body-actorId-not-identity) and needs an attestation
|
|
2668
|
+
// decision, not a schema line.
|
|
2669
|
+
role: z.enum(["user", "assistant"]).optional(),
|
|
2297
2670
|
}),
|
|
2298
2671
|
response: z.object({
|
|
2299
2672
|
ok: z.boolean(),
|
|
@@ -3039,6 +3412,9 @@ export const RECEIVERS = {
|
|
|
3039
3412
|
actor: z.string(), chain: z.string(), address: z.string(), balance: z.string().nullable(),
|
|
3040
3413
|
})).optional(),
|
|
3041
3414
|
addresses: z.array(z.object({ actor: z.string(), chain: z.string(), address: z.string() })).optional(),
|
|
3415
|
+
// The per-agent PROJECTION of the on-chain ceiling (text/launch.md §0d).
|
|
3416
|
+
// Zod strips unknown keys, so a field absent here never reaches AgentCaps.
|
|
3417
|
+
agentCaps: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
3042
3418
|
delegated: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
3043
3419
|
error: z.string().optional(),
|
|
3044
3420
|
}),
|
|
@@ -3063,6 +3439,37 @@ export const RECEIVERS = {
|
|
|
3063
3439
|
effect: "ask", cost: "free", reversible: true, idempotent: true, settles: "none", auth: "member",
|
|
3064
3440
|
examples: [{ limit: 20 }, { workspace: "acme", limit: 100 }],
|
|
3065
3441
|
}),
|
|
3442
|
+
// wallet:transfer — INTERNAL credits only. TRANSFER, NEVER MINT: a
|
|
3443
|
+
// solvency-checked debit and a credit that sum to zero, so a compromised caller
|
|
3444
|
+
// can move credits around but cannot create them. Bounded by the payer's cap so
|
|
3445
|
+
// an agent cannot transfer its way past its ceiling. Deliberately NOT
|
|
3446
|
+
// `surfaces: { mcp: true }` — it never touches a chain and settles in the
|
|
3447
|
+
// ledger, so it declares `settles: "offchain"` and stays reversible by a
|
|
3448
|
+
// compensating transfer, unlike wallet:send.
|
|
3449
|
+
"wallet:transfer": receiver({
|
|
3450
|
+
receiver: "wallet:transfer",
|
|
3451
|
+
summary: "Move credits between workspaces inside the ledger — solvency-checked, cap-bounded, zero-sum. Never mints; never touches a chain",
|
|
3452
|
+
request: z.object({
|
|
3453
|
+
to: z.string().describe("Recipient workspace slug"),
|
|
3454
|
+
amount: z.number().describe("Credits; must be > 0 and an integer"),
|
|
3455
|
+
workspace: z.string().optional().describe("Payer workspace; falls back to `from`, then the attested caller"),
|
|
3456
|
+
from: z.string().optional().describe("Alias for `workspace` — the source the authority walk runs against"),
|
|
3457
|
+
transferId: z.string().optional().describe("Idempotency key; both legs share it as rail_ref"),
|
|
3458
|
+
memo: z.string().optional(),
|
|
3459
|
+
}),
|
|
3460
|
+
response: z.object({
|
|
3461
|
+
ok: z.boolean(),
|
|
3462
|
+
from: z.string().optional(),
|
|
3463
|
+
to: z.string().optional(),
|
|
3464
|
+
amount: z.number().optional(),
|
|
3465
|
+
transferId: z.string().optional(),
|
|
3466
|
+
error: z.string().optional().describe("forbidden | insufficient_credits | cap_exceeded | invalid_amount"),
|
|
3467
|
+
}),
|
|
3468
|
+
// Zero-sum inside the credit ledger — real value, settled OFFCHAIN (never on
|
|
3469
|
+
// chain), reversible by a compensating transfer. Idempotent on `transferId`.
|
|
3470
|
+
effect: "ask", cost: "free", reversible: true, idempotent: true, settles: "offchain", auth: "manage_workspace",
|
|
3471
|
+
examples: [{ to: "acme", amount: 500 }],
|
|
3472
|
+
}),
|
|
3066
3473
|
// The caller signs and broadcasts the transfer CLIENT-SIDE and passes the
|
|
3067
3474
|
// resulting `paymentTx`; this composes pay.one.ie's create → quote → claim and
|
|
3068
3475
|
// never holds a key. It VERIFIES that an on-chain transfer settled, so it
|
|
@@ -3930,7 +4337,7 @@ export const RECEIVERS = {
|
|
|
3930
4337
|
"workflow:step-stats": receiver({
|
|
3931
4338
|
receiver: "workflow:step-stats",
|
|
3932
4339
|
surfaces: { mcp: true },
|
|
3933
|
-
summary: "Per-step run counts and
|
|
4340
|
+
summary: "Per-step run counts, latency and last messages for one workflow (over D1 workflow_run_event) — what the canvas prints on each step card",
|
|
3934
4341
|
request: z.object({ workflowId: z.string(), sinceMs: z.number().int().optional() }),
|
|
3935
4342
|
response: z.object({
|
|
3936
4343
|
stats: z.array(z.object({
|
|
@@ -3938,8 +4345,28 @@ export const RECEIVERS = {
|
|
|
3938
4345
|
runs: z.number().int(),
|
|
3939
4346
|
failures: z.number().int(),
|
|
3940
4347
|
avgMs: z.number().nullable(),
|
|
4348
|
+
minMs: z.number().nullable(),
|
|
3941
4349
|
maxMs: z.number().nullable(),
|
|
4350
|
+
/** Total ms this step has spent across every completed run. */
|
|
4351
|
+
totalMs: z.number(),
|
|
4352
|
+
/** This step's slice of the whole workflow's execution time, 0-1 — which
|
|
4353
|
+
* step is the bottleneck. null when nothing has completed yet (never 0:
|
|
4354
|
+
* no share of nothing is not "0% of the time"). */
|
|
4355
|
+
share: z.number().nullable(),
|
|
3942
4356
|
lastAt: z.number().nullable(),
|
|
4357
|
+
// The step's last few events, newest first — what it SAID, not just how
|
|
4358
|
+
// often it ran. `text` is already truncated server-side; a card that wants
|
|
4359
|
+
// the whole payload opens the run monitor.
|
|
4360
|
+
messages: z.array(z.object({
|
|
4361
|
+
kind: z.string(),
|
|
4362
|
+
status: z.string().nullable(),
|
|
4363
|
+
latencyMs: z.number().nullable(),
|
|
4364
|
+
at: z.number(),
|
|
4365
|
+
text: z.string(),
|
|
4366
|
+
})),
|
|
4367
|
+
/** Recent completed latencies, OLDEST → NEWEST, for the card's sparkline.
|
|
4368
|
+
* Empty when the step has completed nothing recently. */
|
|
4369
|
+
series: z.array(z.number()),
|
|
3943
4370
|
})),
|
|
3944
4371
|
error: z.string().optional(),
|
|
3945
4372
|
}),
|
|
@@ -5039,6 +5466,50 @@ export const RECEIVERS = {
|
|
|
5039
5466
|
effect: "ask", cost: "variable", reversible: true, idempotent: false, auth: "member",
|
|
5040
5467
|
examples: [{ site_url: "https://one.ie/", start_date: "2026-01-01", end_date: "2026-06-01" }],
|
|
5041
5468
|
}),
|
|
5469
|
+
// ── promise: — the contract a story signs (resolvers/promises.ts) ──────────
|
|
5470
|
+
// Declared here because a workflow step may only bind a REGISTERED name:
|
|
5471
|
+
// `story-intake` binds both, and campaign-ads-social.test.ts refuses a binding
|
|
5472
|
+
// no receiver implements. `promise:get` is deliberately NOT declared — nothing
|
|
5473
|
+
// binds it and this cycle registers only what a template reaches.
|
|
5474
|
+
"promise:make": receiver({
|
|
5475
|
+
receiver: "promise:make",
|
|
5476
|
+
summary: "State terms and exactly one proof observable — the terms FREEZE (sha-256 terms_hash, insert-once) and the promise enters state 'promised'. The maker is the attested caller, never a body field",
|
|
5477
|
+
request: z.object({
|
|
5478
|
+
slug: z.string().describe("Stable id for the promise, e.g. story:<origin>. [A-Za-z0-9_:/-], max 128"),
|
|
5479
|
+
terms: z.string().describe("What is promised — hashed verbatim into terms_hash and never rewritten"),
|
|
5480
|
+
proof: z.string().describe("The ONE observable that decides kept vs broken. A shell-shaped proof settles at PROVE via the CLI, never in the worker"),
|
|
5481
|
+
}),
|
|
5482
|
+
response: z.object({
|
|
5483
|
+
ok: z.boolean(),
|
|
5484
|
+
slug: z.string().optional(),
|
|
5485
|
+
state: z.literal("promised").optional(),
|
|
5486
|
+
terms_hash: z.string().optional(),
|
|
5487
|
+
error: z.string().optional(),
|
|
5488
|
+
}),
|
|
5489
|
+
effect: "ask", cost: "free", auth: "member",
|
|
5490
|
+
reversible: false, idempotent: true, settles: "none",
|
|
5491
|
+
examples: [{ slug: "story:demo", terms: "a mover in Austin tells her origin story on her own page", proof: "test -f one.ie/web/src/pages/story.astro" }],
|
|
5492
|
+
}),
|
|
5493
|
+
"promise:settle": receiver({
|
|
5494
|
+
receiver: "promise:settle",
|
|
5495
|
+
summary: "Close the loop on a promise — kept marks the path, broken warns it, and a terminal promise never re-settles. Only the PERSISTED maker may settle; the verdict is the maker's assertion, the resolver never runs the proof",
|
|
5496
|
+
request: z.object({
|
|
5497
|
+
slug: z.string(),
|
|
5498
|
+
kept: z.boolean().optional().describe("Defaults to true. false ⇒ settled-broken: resistance += 1, the path decays honestly"),
|
|
5499
|
+
composite: z.number().optional().describe("Rubric composite; scales the mark by 5× when kept"),
|
|
5500
|
+
}),
|
|
5501
|
+
response: z.object({
|
|
5502
|
+
ok: z.boolean(),
|
|
5503
|
+
slug: z.string().optional(),
|
|
5504
|
+
state: z.enum(["settled-kept", "settled-broken", "dissolved"]).optional(),
|
|
5505
|
+
strength: z.number().optional(),
|
|
5506
|
+
resistance: z.number().optional(),
|
|
5507
|
+
error: z.string().optional(),
|
|
5508
|
+
}),
|
|
5509
|
+
effect: "ask", cost: "free", auth: "member",
|
|
5510
|
+
reversible: false, idempotent: false, settles: "none",
|
|
5511
|
+
examples: [{ slug: "story:demo", kept: false }],
|
|
5512
|
+
}),
|
|
5042
5513
|
};
|
|
5043
5514
|
/**
|
|
5044
5515
|
* RECIPES — the four agent journeys as typed, ordered receiver sequences (C6).
|