@crewhaus/spec 0.4.0 → 0.4.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/dist/index.d.ts +831 -22
- package/dist/index.js +154 -9
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1325,6 +1325,57 @@ const observabilityBlock = z
|
|
|
1325
1325
|
})
|
|
1326
1326
|
.strict()
|
|
1327
1327
|
.optional();
|
|
1328
|
+
/**
|
|
1329
|
+
* "Watch me" — the sampled phase-2 judge pass of `crewhaus watchme report`,
|
|
1330
|
+
* the ONE model-spending analysis phase. Absent ⇒ deterministic-only reports
|
|
1331
|
+
* (the field defaults below still resolve at lower time, so a budgeted judge
|
|
1332
|
+
* always names a model).
|
|
1333
|
+
*/
|
|
1334
|
+
const watchmeJudgeBlock = z
|
|
1335
|
+
.object({
|
|
1336
|
+
/** Judge model for the sampled phase-2 quality pass. Refused at runtime
|
|
1337
|
+
* if unpriced (dream-engine pattern) — the budget cap must be
|
|
1338
|
+
* enforceable. */
|
|
1339
|
+
model: z.string().min(1).default("claude-haiku-4-5"),
|
|
1340
|
+
/** Fraction of ungraded turns escalated to the judge. */
|
|
1341
|
+
sample_rate: z.number().min(0).max(1).default(0.15),
|
|
1342
|
+
/** Per-report spend cap. 0 (default) = deterministic-only reports. */
|
|
1343
|
+
budget_usd: z.number().min(0).default(0),
|
|
1344
|
+
})
|
|
1345
|
+
.strict();
|
|
1346
|
+
/**
|
|
1347
|
+
* "Watch me" — observe this harness's interactions and learn from them
|
|
1348
|
+
* (design/watch-me.md). Presence turns on the live capture tap; `crewhaus
|
|
1349
|
+
* watchme report` distills the watched sessions post-hoc. Carried on the
|
|
1350
|
+
* three interactive-loop shapes (cli, channel, managed); the strict unions
|
|
1351
|
+
* reject the key loudly elsewhere (research/crew are a named deferral,
|
|
1352
|
+
* design/watch-me.md §13.1).
|
|
1353
|
+
*
|
|
1354
|
+
* Deliberately a SIBLING of `observability:`, not a sub-key of it:
|
|
1355
|
+
* observability controls the generic telemetry subscribers (ring buffer,
|
|
1356
|
+
* printers, metrics, cost, alerts, otel) while watchme is a learning feature
|
|
1357
|
+
* with its own durable store and spec-synthesis outputs. Capture is
|
|
1358
|
+
* INDEPENDENT of `observability.trace.level` — that knob controls the ring
|
|
1359
|
+
* buffer + printers only, never the watchme tap.
|
|
1360
|
+
*
|
|
1361
|
+
* Every knob defaults, so a bare `watchme: {}` is a complete declaration.
|
|
1362
|
+
* NO `watchme.*` path is optimizer-tunable — see the exclusion note beside
|
|
1363
|
+
* OPTIMIZABLE_PATHS in `@crewhaus/spec-patch`.
|
|
1364
|
+
*/
|
|
1365
|
+
const watchmeBlock = z
|
|
1366
|
+
.object({
|
|
1367
|
+
enabled: z.boolean().default(true),
|
|
1368
|
+
/** "full" = write the .events.jsonl trace sibling; "mirrors" = rely on the
|
|
1369
|
+
* default-on advisor mirrors only (retro-analysis grade, no extra file). */
|
|
1370
|
+
capture: z.enum(["full", "mirrors"]).default("full"),
|
|
1371
|
+
judge: watchmeJudgeBlock.optional(),
|
|
1372
|
+
/** "user" additionally registers this harness in the global registry at run time. */
|
|
1373
|
+
scope: z.enum(["harness", "user"]).default("harness"),
|
|
1374
|
+
/** Publish redacted distilled findings to the wiki/Thredz at report time. */
|
|
1375
|
+
share: z.boolean().default(false),
|
|
1376
|
+
})
|
|
1377
|
+
.strict()
|
|
1378
|
+
.optional();
|
|
1328
1379
|
/**
|
|
1329
1380
|
* Section 47 — blockchain subsystem blocks (cross-cutting). Any shape may
|
|
1330
1381
|
* declare any subset of `chains` / `wallets` / `contracts` /
|
|
@@ -1396,10 +1447,13 @@ const contractsBlock = z.array(contractBindingSchema).optional();
|
|
|
1396
1447
|
const transactionPolicyBlock = transactionPolicySchema.optional();
|
|
1397
1448
|
/**
|
|
1398
1449
|
* Phase 3 §3.3 — CLI banner with optional tagline rotation. When set,
|
|
1399
|
-
*
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
*
|
|
1450
|
+
* BOTH cli surfaces print this banner on cold start — the compiled
|
|
1451
|
+
* bundle and `crewhaus run` (which used to ignore the block entirely,
|
|
1452
|
+
* making an authored banner invisible to anyone who ran the spec
|
|
1453
|
+
* directly). Suppressed under `--resume` / `--continue` so resumed
|
|
1454
|
+
* sessions don't re-banner, and under `CREWHAUS_RESUMED=1` for a
|
|
1455
|
+
* wrapper re-invoking a compiled bundle. Static mode picks the first
|
|
1456
|
+
* tagline; random mode picks one uniformly per startup.
|
|
1403
1457
|
*/
|
|
1404
1458
|
const cliBannerBlock = z
|
|
1405
1459
|
.object({
|
|
@@ -1602,6 +1656,8 @@ const cliSchema = z
|
|
|
1602
1656
|
thredz: thredzBlock,
|
|
1603
1657
|
learning: learningBlock,
|
|
1604
1658
|
observability: observabilityBlock,
|
|
1659
|
+
// "Watch me" — observe-and-learn (sibling of observability, see watchmeBlock).
|
|
1660
|
+
watchme: watchmeBlock,
|
|
1605
1661
|
cli: cliOptionsBlock,
|
|
1606
1662
|
chains: chainsBlock,
|
|
1607
1663
|
wallets: walletsBlock,
|
|
@@ -1710,6 +1766,15 @@ const whatsappChannelSchema = z
|
|
|
1710
1766
|
phoneNumberId: z.string().min(1),
|
|
1711
1767
|
accessToken: z.string().min(1),
|
|
1712
1768
|
appSecret: z.string().min(1),
|
|
1769
|
+
// The token Meta presents on the GET callback-URL verification handshake
|
|
1770
|
+
// (`hub.verify_token`). Optional: a daemon serving an already-verified
|
|
1771
|
+
// subscription does not need it, and without it the handshake fails
|
|
1772
|
+
// closed rather than echoing an unauthenticated challenge.
|
|
1773
|
+
verifyToken: z
|
|
1774
|
+
.string()
|
|
1775
|
+
.min(1)
|
|
1776
|
+
.optional()
|
|
1777
|
+
.describe("shared token echoed back on Meta's GET callback-URL verification handshake; required to verify a new webhook subscription"),
|
|
1713
1778
|
})
|
|
1714
1779
|
.strict();
|
|
1715
1780
|
const imessageChannelSchema = z
|
|
@@ -1792,6 +1857,8 @@ const channelSchema = z
|
|
|
1792
1857
|
thredz: thredzBlock,
|
|
1793
1858
|
learning: learningBlock,
|
|
1794
1859
|
observability: observabilityBlock,
|
|
1860
|
+
// "Watch me" — observe-and-learn (sibling of observability, see watchmeBlock).
|
|
1861
|
+
watchme: watchmeBlock,
|
|
1795
1862
|
heartbeat: heartbeatBlock,
|
|
1796
1863
|
// Loop contract 0.4 (Batch F) — cron/interval wake trigger (the general
|
|
1797
1864
|
// temporal surface beside the interval-only `heartbeat`).
|
|
@@ -1818,9 +1885,28 @@ const graphNodeSchema = z
|
|
|
1818
1885
|
tools: z.array(z.string().min(1)).optional(),
|
|
1819
1886
|
tool_config: toolConfigBlock,
|
|
1820
1887
|
/**
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
1823
|
-
*
|
|
1888
|
+
* A human approval gate on this node, and a PRE-condition: the node
|
|
1889
|
+
* calls `ctx.requestApproval(prompt)` BEFORE its model turn, so the
|
|
1890
|
+
* prompt is answered against the UPSTREAM state (which the `hitl_pause`
|
|
1891
|
+
* event and the bundle's pause report both print) and no tokens are
|
|
1892
|
+
* spent until the human answers. The engine pauses, persists a
|
|
1893
|
+
* checkpoint, and waits for `resume(checkpointId, decision)` from the
|
|
1894
|
+
* operator/CLI; the resumed run replays this node from the top and
|
|
1895
|
+
* makes its FIRST model call.
|
|
1896
|
+
*
|
|
1897
|
+
* The decision string is recorded at `state["<node>_decision"]`, which
|
|
1898
|
+
* every downstream node reads as part of the upstream state. (NOTE:
|
|
1899
|
+
* `edges[].when.key` cannot name it yet — that key must name a declared
|
|
1900
|
+
* node; see the `graphEdgeWhenSchema` note below.) A rejecting decision
|
|
1901
|
+
* — `reject`, `no`, `deny`, `decline`, `abort`, `cancel`, `stop`, `veto`
|
|
1902
|
+
* (trimmed, case-insensitive) — cancels this node's turn entirely, so
|
|
1903
|
+
* the node records only its decision and no output; any other string,
|
|
1904
|
+
* including free text, approves it. To halt the run on a rejection,
|
|
1905
|
+
* guard the node's outgoing edge with `when: { key: <node>, exists:
|
|
1906
|
+
* true }` — a cancelled node records no output, so no edge matches.
|
|
1907
|
+
*
|
|
1908
|
+
* To have a human approve a node's OWN output, put the gate on the
|
|
1909
|
+
* DOWNSTREAM node: its upstream state is exactly that output.
|
|
1824
1910
|
*/
|
|
1825
1911
|
hitl: z
|
|
1826
1912
|
.object({
|
|
@@ -1857,8 +1943,16 @@ const graphAnyNodeSchema = z.union([graphNodeSchema, graphJudgeNodeSchema]);
|
|
|
1857
1943
|
* - `equals` — take the edge when `state[key] === equals` (string/number/
|
|
1858
1944
|
* boolean strict equality).
|
|
1859
1945
|
* - `exists: true` — take the edge when `state[key] !== undefined` (the
|
|
1860
|
-
* node has produced output
|
|
1861
|
-
*
|
|
1946
|
+
* node has produced output — which, for a `hitl:` node, is FALSE when
|
|
1947
|
+
* the operator rejected the gate, since a rejected node records only
|
|
1948
|
+
* `state["<node>_decision"]`).
|
|
1949
|
+
*
|
|
1950
|
+
* GAP (unchanged by the pre-condition HITL fix): `key` may not yet name a
|
|
1951
|
+
* hitl node's `<node>_decision` record — the cross-check below pins it to a
|
|
1952
|
+
* declared node name, so a rejection can be observed via `exists` on the
|
|
1953
|
+
* node itself but not matched on the decision string. Widening it means
|
|
1954
|
+
* touching the three mirrored checks (this one, ir-passes' graph
|
|
1955
|
+
* wellformedness, target-graph's validateGraph).
|
|
1862
1956
|
*
|
|
1863
1957
|
* Lowered to `IrGraphEdge.when` and emitted as a graph-engine
|
|
1864
1958
|
* `EdgeCondition` (`(state) => state[key] === equals` / `!== undefined`).
|
|
@@ -1979,6 +2073,23 @@ const managedSchema = z
|
|
|
1979
2073
|
expose: exposeBlock,
|
|
1980
2074
|
// Loop contract 0.4 (Batch B, G02) — in-loop output evaluation.
|
|
1981
2075
|
evaluation: evaluationBlock,
|
|
2076
|
+
// NEW-inloop-coverage — human-rating capture on the GATEWAY shape.
|
|
2077
|
+
//
|
|
2078
|
+
// WHAT MANAGED SUPPORTS: the daemon serves a `feedback.submit` JSON-RPC
|
|
2079
|
+
// method (params = the user-supplied FeedbackRecord subset) that appends
|
|
2080
|
+
// a standard record to `.crewhaus/feedback/<tenant>.jsonl` — the exact
|
|
2081
|
+
// sink `crewhaus distill` / `optimize --ratings` / `judge calibrate`
|
|
2082
|
+
// already read; and `autoDistill: true` registers the janitor step that
|
|
2083
|
+
// turns those ratings into versioned `<name>-ratings` registry datasets
|
|
2084
|
+
// on the daemon's own clock (D39), because a gateway daemon never runs a
|
|
2085
|
+
// `crewhaus run` teardown.
|
|
2086
|
+
//
|
|
2087
|
+
// WHAT IT CANNOT SUPPORT: `exitPrompt` is meaningless here (there is no
|
|
2088
|
+
// REPL to exit — the compiler warns when it is set), and
|
|
2089
|
+
// `channelReactions` is the channel shape's own inbound-reaction gate
|
|
2090
|
+
// (also warned). Both parse for schema uniformity across shapes rather
|
|
2091
|
+
// than being silently honoured.
|
|
2092
|
+
feedback: feedbackBlock,
|
|
1982
2093
|
memory: memoryBlock,
|
|
1983
2094
|
// Loop contract 0.4 (Batch E, G22) — agent-shape RAG over doc sources.
|
|
1984
2095
|
knowledge: knowledgeBlock,
|
|
@@ -1986,6 +2097,9 @@ const managedSchema = z
|
|
|
1986
2097
|
thredz: thredzBlock,
|
|
1987
2098
|
learning: learningBlock,
|
|
1988
2099
|
observability: observabilityBlock,
|
|
2100
|
+
// "Watch me" — observe-and-learn (sibling of observability, see watchmeBlock).
|
|
2101
|
+
// Parse + lower ONLY on this shape in v1: compile() warns accepted-but-unwired.
|
|
2102
|
+
watchme: watchmeBlock,
|
|
1989
2103
|
// Loop contract 0.4 (Batch F) — cron/interval wake trigger.
|
|
1990
2104
|
schedule: scheduleBlock,
|
|
1991
2105
|
})
|
|
@@ -2287,6 +2401,22 @@ const browserDriverSchema = z
|
|
|
2287
2401
|
.strict()
|
|
2288
2402
|
.default({ width: 1280, height: 720 }),
|
|
2289
2403
|
startUrl: z.string().url().optional(),
|
|
2404
|
+
/**
|
|
2405
|
+
* SECURITY — opt in to private/loopback navigation targets. Default false:
|
|
2406
|
+
* the Navigate tool refuses private/loopback/link-local/metadata hosts
|
|
2407
|
+
* before `driver.goto`, and the chromium backend routes every request
|
|
2408
|
+
* through a DNS-pinning proxy that refuses the same floor at the
|
|
2409
|
+
* connection layer. Together they stop a prompt-injected page from
|
|
2410
|
+
* reaching the host's own services.
|
|
2411
|
+
*
|
|
2412
|
+
* Set true ONLY when the browser legitimately must reach a private target
|
|
2413
|
+
* the operator controls AND the page content is trusted — an intranet app
|
|
2414
|
+
* under test, or a locally-served fixture page (what the browser runtime
|
|
2415
|
+
* smoke does). It relaxes BOTH layers for this spec, so it stays a
|
|
2416
|
+
* per-spec reviewed decision and never a global switch. The http/https
|
|
2417
|
+
* scheme allowlist is NOT waived.
|
|
2418
|
+
*/
|
|
2419
|
+
allowPrivateTargets: z.boolean().default(false),
|
|
2290
2420
|
})
|
|
2291
2421
|
.strict();
|
|
2292
2422
|
const browserSchema = z
|
|
@@ -2597,6 +2727,21 @@ function crossFieldIssues(data) {
|
|
|
2597
2727
|
custom(["retrieve", "collection"], `pipeline retrieve.vectorBackend "${vectorBackend}" requires retrieve.collection`);
|
|
2598
2728
|
}
|
|
2599
2729
|
}
|
|
2730
|
+
// "Watch me" (design/watch-me.md §4.2) — `watchme.share: true` publishes
|
|
2731
|
+
// co-learning articles, so it conflicts with a thredz OBJECT that declares
|
|
2732
|
+
// an EXPLICIT `visibility: "private"`. The boolean/string shorthands
|
|
2733
|
+
// (default-private) get NO issue in v1 — publishing then lands
|
|
2734
|
+
// private-visibility articles, legal single-agent behaviour — and an
|
|
2735
|
+
// absent `thredz:` block is fine (publish degrades to the local wiki
|
|
2736
|
+
// store, a feature not an error).
|
|
2737
|
+
if (data.target === "cli" || data.target === "channel" || data.target === "managed") {
|
|
2738
|
+
const thredz = data.thredz;
|
|
2739
|
+
if (data.watchme?.share === true &&
|
|
2740
|
+
typeof thredz === "object" &&
|
|
2741
|
+
thredz.visibility === "private") {
|
|
2742
|
+
custom(["watchme", "share"], "watchme.share publishes co-learning articles; thredz.visibility: private blocks cross-agent sharing — set visibility: shared or drop watchme.share");
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2600
2745
|
return issues;
|
|
2601
2746
|
}
|
|
2602
2747
|
export function parseSpec(yamlText) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/spec",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "User-facing spec schema (Zod) + YAML parser",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"test": "bun test src"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@crewhaus/errors": "0.4.
|
|
18
|
+
"@crewhaus/errors": "0.4.2",
|
|
19
19
|
"yaml": "^2.6.0",
|
|
20
20
|
"zod": "^3.23.8",
|
|
21
21
|
"zod-to-json-schema": "^3.23.5"
|