@phosphorco/bb-jev 0.0.0-stage → 0.1.0-alpha.1

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/CONTRACT.md ADDED
@@ -0,0 +1,17 @@
1
+ # Shared JEV transport v1
2
+
3
+ This package owns bounded JSON DTOs and intended-owner RPC adapters. It owns no credential, storage, inference policy, admission gate, timer or retry engine. Root imports Zod only; `/bb` has SDK type imports only; `/testing` drives real adopter ports with caller-supplied inert work.
4
+
5
+ Topics (`jev-topics`) is the first explicit connection owner. Its existing secret setting remains local. `sharedJevDescribeV1(null)` advertises protocol `bb-jev`, versions, owner/configured metadata, the nonsecret setting field/source, supported named work and limits. A consumer uses listed plugin discovery for that explicit owner; absence/disabled/incompatibility is an operational error, never permission to scan credentials or switch owners.
6
+
7
+ `sharedJevSubmitV1` takes version1, exact namespace/operationId, consumer/work kind/opaque attribution, finite JSON wire, absolute deadline and response bound. Inputs are strict. A complete canonical SHA256 input fingerprint binds wire, work, attribution, operation, deadline and budgets. No URL/header/key parameter exists. Owner storage must reserve that immutable request before work and persist effect-possible before its fixed JEV HTTP POST. Same operation+input returns retained facts, changed input conflicts. Invoked or uncertain work must never be automatically relaunched, including after process/plugin restart. An interrupted effect-possible row becomes uncertain. Consumer policy/budget/evaluation ledgers remain consumer-owned.
8
+
9
+ `sharedJevLookupV1` takes version+exact operation and returns null or current record. Results are additive at evolving object boundaries. Opaque attribution/response JSON remains exact. States distinguish positive not-invoked from possible invocation, succeeded transport, failed transport and uncertain effects. Success is transport evidence, never semantic policy approval. Usage preserves provider-reported token counts and opaque reported cost (or null); missing/invalid usage remains explicit. No price or token estimate is invented. Raw response and bounded HTTP error context are retained, masking only the exact owner credential. Work attribution identifies MR admission/suite and their attempt/partition separately from Topics assessment work.
10
+
11
+ Package caps: request1MiB, raw response1MiB, response envelope1MiB+64KiB, describe16KiB, JSON depth48, max absolute execution budget60s and4 physical unsettled calls per client lane (submit, ordinary read, reconciliation read; maximum12 RPCs). Owner advertises/enforces its physical limit independently and retains a slot until HTTP/body work actually settles. Abort/timeout only fences a local observation; it does not establish non-execution. The adapter attempts at most ONE independent bounded lookup after failed submit, using separate reconciliation capacity so saturated writes cannot starve the first reconciliation wave. If that finite read capacity is also occupied, it refuses locally and retains unknown; it never retries submit. A lookup failure/null remains unknown.
12
+
13
+ No cancel-write method is provided in v1: cancelling an observer or MR evaluation cannot undo an already invoked HTTP call. The service owns its deadline and disposal abort. Late terminal HTTP observations may settle the owner's transport journal, while a retired MR assessment remains fenced by its own ledger.
14
+
15
+ Provider must preserve existing Topics scopes/assessment behavior and serve shared named work independently of an assessment profile. Initial named work is `message-rules.admission` and `message-rules.suite`. Dedicated MR override remains an explicit consumer selection and is not a service credential source.
16
+
17
+ Proof layers: package emitted/packed tests, real owner SQLite+RPC conformance, actual MR adopter ledger tests and loaded cross-plugin inference are separate. No package test establishes loaded runtime, native admission, provider execution or billing correctness.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phosphor
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,9 @@
1
- # Temporary Holding Version
1
+ # @phosphorco/bb-jev
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Versioned shared JEV transport without exporting credentials. The connection owner keeps its key and durable operation journal; consumers keep their own policy, budgets and evaluation ledgers.
4
+
5
+ - Root: strict request/additive result schemas, JSON bounds and fingerprints.
6
+ - `/bb`: owner registration and an explicit-owner client with one lookup after unknown submit, no write retry.
7
+ - `/testing`: anchored immutable-operation conformance for real adopter handlers.
8
+
9
+ See [CONTRACT.md](CONTRACT.md) for exact methods, limits, ownership and uncertainty semantics. Version0.1.0-alpha.1 is a prerelease; source checks do not establish registry or live adoption.
package/dist/bb.js ADDED
@@ -0,0 +1,155 @@
1
+ // src/bb.ts
2
+ import { z } from "zod";
3
+ import * as p from "./protocol.js";
4
+ function sdkSchema(schema) {
5
+ return { "~standard": {
6
+ version: 1,
7
+ vendor: "bb-jev",
8
+ jsonSchema: {
9
+ input: () => schema.toJSONSchema({ io: "input", unrepresentable: "any" }),
10
+ output: () => schema.toJSONSchema({ io: "output", unrepresentable: "any" })
11
+ },
12
+ validate(value) {
13
+ const parsed = schema.safeParse(value);
14
+ return parsed.success ? { value: parsed.data } : { issues: parsed.error.issues.map((issue) => ({ message: issue.message, path: issue.path.map((k) => typeof k === "symbol" ? String(k) : k) })) };
15
+ }
16
+ } };
17
+ }
18
+ function registerJevOwner(bb, port) {
19
+ bb.rpc.register({
20
+ [p.METHODS.describe]: { input: sdkSchema(z.null()), output: sdkSchema(p.DescribeSchema) },
21
+ [p.METHODS.submit]: { input: sdkSchema(p.SubmitSchema), output: sdkSchema(p.RecordSchema) },
22
+ [p.METHODS.lookup]: { input: sdkSchema(p.LookupSchema), output: sdkSchema(p.RecordSchema.nullable()) }
23
+ }, {
24
+ [p.METHODS.describe]: async () => p.decode(p.DescribeSchema, await port.describe(), p.LIMITS.describeBytes),
25
+ [p.METHODS.submit]: async (input) => {
26
+ const captured = p.decode(p.SubmitSchema, JSON.parse(p.canonical(input, p.LIMITS.requestBytes)), p.LIMITS.requestBytes);
27
+ const record = await p.decodeRecord(captured, await port.submit(captured));
28
+ if (!record)
29
+ throw Error("JEV owner omitted submitted operation");
30
+ return record;
31
+ },
32
+ [p.METHODS.lookup]: async (input) => p.decodeRecord(input, await port.lookup(input))
33
+ }, { experimental_discoverable: true });
34
+ }
35
+
36
+ class JevCallError extends Error {
37
+ outcome;
38
+ original;
39
+ constructor(outcome, message, original) {
40
+ super(message);
41
+ this.outcome = outcome;
42
+ this.original = original;
43
+ this.name = "JevCallError";
44
+ }
45
+ }
46
+ function bounded(run, ms, outer) {
47
+ const ctrl = new AbortController;
48
+ return new Promise((resolve, reject) => {
49
+ let done = false;
50
+ const finish = (fn) => {
51
+ if (done)
52
+ return;
53
+ done = true;
54
+ clearTimeout(timer);
55
+ outer?.removeEventListener("abort", abort);
56
+ fn();
57
+ };
58
+ const abort = () => {
59
+ ctrl.abort();
60
+ finish(() => reject(Error("JEV observation cancelled; remote work may continue")));
61
+ };
62
+ const timer = setTimeout(() => {
63
+ ctrl.abort();
64
+ finish(() => reject(Error("JEV observation deadline; remote work may continue")));
65
+ }, Math.max(1, ms));
66
+ outer?.addEventListener("abort", abort, { once: true });
67
+ if (outer?.aborted) {
68
+ abort();
69
+ return;
70
+ }
71
+ Promise.resolve().then(() => run(ctrl.signal)).then((v) => finish(() => resolve(v)), (e) => finish(() => reject(e)));
72
+ });
73
+ }
74
+ function createJevClient(sdk, ownerPluginId = p.DEFAULT_OWNER) {
75
+ const physical = { submit: 0, read: 0, reconcile: 0 };
76
+ async function call(method, input, ms, signal, reconcile = false) {
77
+ const lane = method === p.METHODS.submit ? "submit" : reconcile ? "reconcile" : "read";
78
+ if (physical[lane] >= p.LIMITS.concurrency)
79
+ throw Error("JEV client unsettled-call capacity exceeded");
80
+ return bounded((s) => {
81
+ if (physical[lane] >= p.LIMITS.concurrency)
82
+ throw Error("JEV client unsettled-call capacity exceeded");
83
+ physical[lane]++;
84
+ let pending;
85
+ try {
86
+ pending = Promise.resolve(sdk.plugins.callRpc({ pluginId: ownerPluginId, method, input, outputSchema: z.unknown(), signal: s }));
87
+ } catch (e) {
88
+ physical[lane]--;
89
+ throw e;
90
+ }
91
+ pending.then(() => physical[lane]--, () => physical[lane]--);
92
+ return pending;
93
+ }, ms, signal);
94
+ }
95
+ async function lookup(operation, signal, reconcile = false) {
96
+ const input = p.LookupSchema.parse({ version: 1, operation });
97
+ try {
98
+ return await p.decodeRecord(input, await call(p.METHODS.lookup, input, 2000, signal, reconcile));
99
+ } catch (e) {
100
+ throw new JevCallError("read-failed", "Shared JEV operation lookup failed", e);
101
+ }
102
+ }
103
+ return {
104
+ ownerPluginId,
105
+ async describe(signal) {
106
+ try {
107
+ const listed = await bounded((s) => sdk.plugins.list({ signal: s }), 2000, signal);
108
+ if (!listed.plugins.some((plugin) => plugin.id === ownerPluginId && plugin.status === "running"))
109
+ throw Error(`Shared JEV owner ${ownerPluginId} is missing, disabled or not running`);
110
+ const raw = await call(p.METHODS.describe, null, 2000, signal);
111
+ const envelope = p.decode(p.DescribeEnvelopeSchema, raw, p.LIMITS.describeBytes);
112
+ if (envelope.protocol !== p.PROTOCOL || !envelope.versions.includes(1))
113
+ throw Error("Shared JEV protocol version is incompatible");
114
+ const value = p.decode(p.DescribeSchema, raw, p.LIMITS.describeBytes);
115
+ if (value.ownerPluginId !== ownerPluginId || value.source.ownerPluginId !== ownerPluginId)
116
+ throw Error("Shared JEV connection owner mismatch");
117
+ return value;
118
+ } catch (e) {
119
+ throw new JevCallError("incompatible", e instanceof Error ? e.message : String(e), e);
120
+ }
121
+ },
122
+ lookup,
123
+ async submit(raw, signal) {
124
+ let input;
125
+ try {
126
+ input = p.decode(p.SubmitSchema, JSON.parse(p.canonical(raw, p.LIMITS.requestBytes)), p.LIMITS.requestBytes);
127
+ if (signal?.aborted || input.deadlineAt <= Date.now())
128
+ throw Error("JEV deadline/cancellation before submit");
129
+ } catch (e) {
130
+ throw new JevCallError("not-invoked", "Shared JEV request refused before submit", e);
131
+ }
132
+ try {
133
+ const value = await p.decodeRecord(input, await call(p.METHODS.submit, input, Math.min(p.LIMITS.deadlineMs, input.deadlineAt - Date.now()) + 250, signal));
134
+ if (!value)
135
+ throw Error("JEV owner returned no operation");
136
+ return value;
137
+ } catch (e) {
138
+ try {
139
+ const retained = await lookup(input.operation, undefined, true);
140
+ if (retained) {
141
+ const checked = await p.decodeRecord(input, retained);
142
+ if (checked)
143
+ return checked;
144
+ }
145
+ } catch {}
146
+ throw new JevCallError("unknown", "Shared JEV submit outcome unknown; retain operation and reconcile without resending", e);
147
+ }
148
+ }
149
+ };
150
+ }
151
+ export {
152
+ registerJevOwner,
153
+ createJevClient,
154
+ JevCallError
155
+ };
@@ -0,0 +1,168 @@
1
+ // src/protocol.ts
2
+ import { z } from "zod";
3
+ var PROTOCOL = "bb-jev";
4
+ var VERSION = 1;
5
+ var DEFAULT_OWNER = "jev-topics";
6
+ var METHODS = Object.freeze({ describe: "sharedJevDescribeV1", submit: "sharedJevSubmitV1", lookup: "sharedJevLookupV1" });
7
+ var LIMITS = Object.freeze({ requestBytes: 1048576, responseBytes: 1114112, wireResponseBytes: 1048576, describeBytes: 16384, depth: 48, deadlineMs: 60000, concurrency: 4 });
8
+ function canonical(value, maxBytes = LIMITS.responseBytes) {
9
+ const seen = new Set;
10
+ let spent = 0;
11
+ const charge = (n) => {
12
+ spent += n;
13
+ if (spent > maxBytes)
14
+ throw Error("JSON byte budget exceeded");
15
+ };
16
+ const leaf = (v) => {
17
+ const text = JSON.stringify(v);
18
+ charge(new TextEncoder().encode(text).byteLength);
19
+ return text;
20
+ };
21
+ function visit(v, depth) {
22
+ if (depth > LIMITS.depth)
23
+ throw Error("JSON depth exceeded");
24
+ if (v === null || typeof v === "boolean" || typeof v === "string")
25
+ return leaf(v);
26
+ if (typeof v === "number" && Number.isFinite(v))
27
+ return leaf(v);
28
+ if (!v || typeof v !== "object" || seen.has(v))
29
+ throw Error("Expected finite acyclic JSON");
30
+ seen.add(v);
31
+ try {
32
+ if (Array.isArray(v)) {
33
+ if (Reflect.ownKeys(v).length !== v.length + 1)
34
+ throw Error("Sparse or decorated JSON array");
35
+ charge(2 + Math.max(0, v.length - 1));
36
+ const elements = [];
37
+ for (let i = 0;i < v.length; i++) {
38
+ const d = Object.getOwnPropertyDescriptor(v, String(i));
39
+ if (!d || !("value" in d))
40
+ throw Error("JSON array accessor");
41
+ elements.push(visit(d.value, depth + 1));
42
+ }
43
+ return "[" + elements.join(",") + "]";
44
+ }
45
+ if (Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null)
46
+ throw Error("Expected plain JSON object");
47
+ if (Reflect.ownKeys(v).some((k) => typeof k !== "string"))
48
+ throw Error("JSON symbol key");
49
+ const keys = Object.keys(v).sort();
50
+ charge(2 + Math.max(0, keys.length - 1));
51
+ return "{" + keys.map((k) => {
52
+ const descriptor = Object.getOwnPropertyDescriptor(v, k);
53
+ if (!descriptor || !("value" in descriptor) || !descriptor.enumerable)
54
+ throw Error("JSON accessor/property");
55
+ charge(new TextEncoder().encode(JSON.stringify(k)).byteLength + 1);
56
+ return JSON.stringify(k) + ":" + visit(descriptor.value, depth + 1);
57
+ }).join(",") + "}";
58
+ } finally {
59
+ seen.delete(v);
60
+ }
61
+ }
62
+ const bytes = visit(value, 0);
63
+ if (new TextEncoder().encode(bytes).byteLength > maxBytes)
64
+ throw Error("JSON byte budget exceeded");
65
+ return bytes;
66
+ }
67
+ async function fingerprint(value) {
68
+ const bytes = new TextEncoder().encode(canonical(value, LIMITS.requestBytes));
69
+ const hash = await globalThis.crypto.subtle.digest("SHA-256", bytes);
70
+ return "sha256:" + Array.from(new Uint8Array(hash), (x) => x.toString(16).padStart(2, "0")).join("");
71
+ }
72
+ var JsonSchema = z.custom((value) => {
73
+ try {
74
+ canonical(value);
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }, "Expected bounded finite plain JSON");
80
+ var id = z.string().min(1).max(256).regex(/^[^\p{Cc}]+$/u);
81
+ var OperationKeySchema = z.strictObject({ namespace: id, operationId: id });
82
+ var SubmitSchema = z.strictObject({
83
+ version: z.literal(1),
84
+ operation: OperationKeySchema,
85
+ work: z.strictObject({ consumerPluginId: id, kind: id, attribution: JsonSchema }),
86
+ wire: JsonSchema,
87
+ deadlineAt: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
88
+ maxResponseBytes: z.number().int().min(1024).max(LIMITS.wireResponseBytes)
89
+ });
90
+ var LookupSchema = z.strictObject({ version: z.literal(1), operation: OperationKeySchema });
91
+ var SourceSchema = z.looseObject({ ownerPluginId: id, kind: z.literal("plugin-setting"), field: id });
92
+ var UsageSchema = z.discriminatedUnion("status", [
93
+ z.looseObject({ status: z.literal("reported"), inputTokens: z.number().int().nonnegative(), outputTokens: z.number().int().nonnegative(), cost: JsonSchema }),
94
+ z.looseObject({ status: z.enum(["missing", "invalid"]), cost: JsonSchema })
95
+ ]);
96
+ var RecordSchema = z.looseObject({
97
+ version: z.literal(1),
98
+ operation: z.looseObject(OperationKeySchema.shape),
99
+ requestFingerprint: z.string().regex(/^sha256:[a-f0-9]{64}$/),
100
+ work: z.looseObject(SubmitSchema.shape.work.shape),
101
+ state: z.enum(["not-invoked", "effect-possible", "succeeded", "failed", "uncertain"]),
102
+ invoked: z.boolean(),
103
+ source: SourceSchema,
104
+ createdAt: z.number().int().nonnegative(),
105
+ settledAt: z.number().int().nonnegative().nullable(),
106
+ latencyMs: z.number().nonnegative().nullable(),
107
+ httpStatus: z.number().int().min(100).max(599).nullable(),
108
+ response: JsonSchema,
109
+ usage: UsageSchema,
110
+ error: z.looseObject({ code: id, message: z.string().max(8192) }).nullable()
111
+ }).superRefine((v, c) => {
112
+ if (v.state === "not-invoked" && v.invoked)
113
+ c.addIssue({ code: "custom", message: "Not-invoked cannot claim invocation" });
114
+ if (v.state !== "not-invoked" && !v.invoked)
115
+ c.addIssue({ code: "custom", message: "Possible invocation must be retained" });
116
+ if (v.state === "effect-possible" && v.settledAt !== null)
117
+ c.addIssue({ code: "custom", message: "Active work cannot be settled" });
118
+ if (v.state !== "effect-possible" && v.settledAt === null)
119
+ c.addIssue({ code: "custom", message: "Final observation requires settlement time" });
120
+ if (v.state === "succeeded" && (v.error !== null || v.response === null))
121
+ c.addIssue({ code: "custom", message: "Success requires a response and no transport error" });
122
+ });
123
+ var DescribeEnvelopeSchema = z.looseObject({ protocol: z.string(), versions: z.array(z.number().int()).max(16) });
124
+ var DescribeSchema = z.looseObject({
125
+ protocol: z.literal(PROTOCOL),
126
+ versions: z.array(z.number().int()).max(16),
127
+ ownerPluginId: id,
128
+ configured: z.boolean(),
129
+ source: SourceSchema,
130
+ workKinds: z.array(id).max(32),
131
+ limits: z.looseObject({ requestBytes: z.number().int().positive(), responseBytes: z.number().int().positive(), deadlineMs: z.number().int().positive(), concurrency: z.number().int().positive() })
132
+ });
133
+ function decode(schema, value, maxBytes = LIMITS.responseBytes) {
134
+ canonical(value, maxBytes);
135
+ return schema.parse(value);
136
+ }
137
+ async function decodeRecord(input, value) {
138
+ if (value === null)
139
+ return null;
140
+ const record = decode(RecordSchema, value);
141
+ if (record.operation.namespace !== input.operation.namespace || record.operation.operationId !== input.operation.operationId)
142
+ throw Error("JEV response operation mismatch");
143
+ if ("wire" in input && record.requestFingerprint !== await fingerprint(input))
144
+ throw Error("JEV response immutable request mismatch");
145
+ if ("wire" in input && canonical({ consumerPluginId: record.work.consumerPluginId, kind: record.work.kind, attribution: record.work.attribution }) !== canonical(input.work))
146
+ throw Error("JEV response work mismatch");
147
+ return record;
148
+ }
149
+ export {
150
+ fingerprint,
151
+ decodeRecord,
152
+ decode,
153
+ canonical,
154
+ VERSION,
155
+ UsageSchema,
156
+ SubmitSchema,
157
+ SourceSchema,
158
+ RecordSchema,
159
+ PROTOCOL,
160
+ OperationKeySchema,
161
+ METHODS,
162
+ LookupSchema,
163
+ LIMITS,
164
+ JsonSchema,
165
+ DescribeSchema,
166
+ DescribeEnvelopeSchema,
167
+ DEFAULT_OWNER
168
+ };
@@ -0,0 +1,27 @@
1
+ // src/testing.ts
2
+ import { canonical, decodeRecord, fingerprint } from "./protocol.js";
3
+ async function runJevConformance(port, input) {
4
+ const initial = await decodeRecord(input, await port.submit(input));
5
+ if (!initial || initial.requestFingerprint !== await fingerprint(input))
6
+ throw Error("Submitted operation not anchored");
7
+ const anchor = (record) => canonical({ operation: { namespace: record.operation.namespace, operationId: record.operation.operationId }, requestFingerprint: record.requestFingerprint, work: { consumerPluginId: record.work.consumerPluginId, kind: record.work.kind, attribution: record.work.attribution }, createdAt: record.createdAt, source: { ownerPluginId: record.source.ownerPluginId, kind: record.source.kind, field: record.source.field } });
8
+ const frozen = anchor(initial);
9
+ const replay = await decodeRecord(input, await port.submit(input));
10
+ if (!replay || anchor(replay) !== frozen)
11
+ throw Error("Same operation replay drifted");
12
+ const lookup = await decodeRecord(input, await port.lookup(input.operation));
13
+ if (!lookup || anchor(lookup) !== frozen)
14
+ throw Error("Operation lookup drifted");
15
+ let conflict = false;
16
+ try {
17
+ await port.submit({ ...input, work: { ...input.work, kind: input.work.kind + "-changed" } });
18
+ } catch {
19
+ conflict = true;
20
+ }
21
+ if (!conflict)
22
+ throw Error("Changed immutable request did not conflict");
23
+ return { cases: ["request-anchored", "immutable-replay", "exact-lookup", "changed-request-conflict"] };
24
+ }
25
+ export {
26
+ runJevConformance
27
+ };
@@ -0,0 +1,65 @@
1
+ import type { BbPluginApi } from '@get-bb/plugin-sdk';
2
+ import { z } from 'zod';
3
+ import * as p from './protocol.js';
4
+ export interface OwnerPort {
5
+ describe(): p.Description | Promise<p.Description>;
6
+ submit(input: p.Submit): Promise<p.OperationRecord>;
7
+ lookup(input: z.infer<typeof p.LookupSchema>): p.OperationRecord | null | Promise<p.OperationRecord | null>;
8
+ }
9
+ export declare function registerJevOwner(bb: Pick<BbPluginApi, 'rpc'>, port: OwnerPort): void;
10
+ export declare class JevCallError extends Error {
11
+ readonly outcome: 'not-invoked' | 'unknown' | 'read-failed' | 'incompatible';
12
+ readonly original?: unknown | undefined;
13
+ constructor(outcome: 'not-invoked' | 'unknown' | 'read-failed' | 'incompatible', message: string, original?: unknown | undefined);
14
+ }
15
+ /** Select one explicit owner. No secret discovery, write retry, or availability polling. */
16
+ export declare function createJevClient(sdk: Pick<BbPluginApi['sdk'], 'plugins'>, ownerPluginId?: string): {
17
+ ownerPluginId: string;
18
+ describe(signal?: AbortSignal): Promise<p.Description>;
19
+ lookup: (operation: p.OperationKey, signal?: AbortSignal, reconcile?: boolean) => Promise<{
20
+ [x: string]: unknown;
21
+ version: 1;
22
+ operation: {
23
+ [x: string]: unknown;
24
+ namespace: string;
25
+ operationId: string;
26
+ };
27
+ requestFingerprint: string;
28
+ work: {
29
+ [x: string]: unknown;
30
+ consumerPluginId: string;
31
+ kind: string;
32
+ attribution: p.Json;
33
+ };
34
+ state: "not-invoked" | "effect-possible" | "succeeded" | "failed" | "uncertain";
35
+ invoked: boolean;
36
+ source: {
37
+ [x: string]: unknown;
38
+ ownerPluginId: string;
39
+ kind: "plugin-setting";
40
+ field: string;
41
+ };
42
+ createdAt: number;
43
+ settledAt: number | null;
44
+ latencyMs: number | null;
45
+ httpStatus: number | null;
46
+ response: p.Json;
47
+ usage: {
48
+ [x: string]: unknown;
49
+ status: "reported";
50
+ inputTokens: number;
51
+ outputTokens: number;
52
+ cost: p.Json;
53
+ } | {
54
+ [x: string]: unknown;
55
+ status: "missing" | "invalid";
56
+ cost: p.Json;
57
+ };
58
+ error: {
59
+ [x: string]: unknown;
60
+ code: string;
61
+ message: string;
62
+ } | null;
63
+ } | null>;
64
+ submit(raw: p.Submit, signal?: AbortSignal): Promise<p.OperationRecord>;
65
+ };
@@ -0,0 +1,146 @@
1
+ import { z } from 'zod';
2
+ export declare const PROTOCOL: "bb-jev";
3
+ export declare const VERSION: 1;
4
+ export declare const DEFAULT_OWNER = "jev-topics";
5
+ export declare const METHODS: Readonly<{
6
+ describe: "sharedJevDescribeV1";
7
+ submit: "sharedJevSubmitV1";
8
+ lookup: "sharedJevLookupV1";
9
+ }>;
10
+ export declare const LIMITS: Readonly<{
11
+ requestBytes: 1048576;
12
+ responseBytes: 1114112;
13
+ wireResponseBytes: 1048576;
14
+ describeBytes: 16384;
15
+ depth: 48;
16
+ deadlineMs: 60000;
17
+ concurrency: 4;
18
+ }>;
19
+ export type Json = null | boolean | number | string | Json[] | {
20
+ [key: string]: Json;
21
+ };
22
+ /** Validate finite plain JSON before decoding; never invoke toJSON or silently drop undefined. */
23
+ export declare function canonical(value: unknown, maxBytes?: number): string;
24
+ export declare function fingerprint(value: unknown): Promise<string>;
25
+ export declare const JsonSchema: z.ZodType<Json>;
26
+ export declare const OperationKeySchema: z.ZodObject<{
27
+ namespace: z.ZodString;
28
+ operationId: z.ZodString;
29
+ }, z.core.$strict>;
30
+ export type OperationKey = z.infer<typeof OperationKeySchema>;
31
+ export declare const SubmitSchema: z.ZodObject<{
32
+ version: z.ZodLiteral<1>;
33
+ operation: z.ZodObject<{
34
+ namespace: z.ZodString;
35
+ operationId: z.ZodString;
36
+ }, z.core.$strict>;
37
+ work: z.ZodObject<{
38
+ consumerPluginId: z.ZodString;
39
+ kind: z.ZodString;
40
+ attribution: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
41
+ }, z.core.$strict>;
42
+ wire: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
43
+ deadlineAt: z.ZodNumber;
44
+ maxResponseBytes: z.ZodNumber;
45
+ }, z.core.$strict>;
46
+ export type Submit = z.infer<typeof SubmitSchema>;
47
+ export declare const LookupSchema: z.ZodObject<{
48
+ version: z.ZodLiteral<1>;
49
+ operation: z.ZodObject<{
50
+ namespace: z.ZodString;
51
+ operationId: z.ZodString;
52
+ }, z.core.$strict>;
53
+ }, z.core.$strict>;
54
+ export declare const SourceSchema: z.ZodObject<{
55
+ ownerPluginId: z.ZodString;
56
+ kind: z.ZodLiteral<"plugin-setting">;
57
+ field: z.ZodString;
58
+ }, z.core.$loose>;
59
+ export declare const UsageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
60
+ status: z.ZodLiteral<"reported">;
61
+ inputTokens: z.ZodNumber;
62
+ outputTokens: z.ZodNumber;
63
+ cost: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
64
+ }, z.core.$loose>, z.ZodObject<{
65
+ status: z.ZodEnum<{
66
+ missing: "missing";
67
+ invalid: "invalid";
68
+ }>;
69
+ cost: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
70
+ }, z.core.$loose>], "status">;
71
+ export declare const RecordSchema: z.ZodObject<{
72
+ version: z.ZodLiteral<1>;
73
+ operation: z.ZodObject<{
74
+ namespace: z.ZodString;
75
+ operationId: z.ZodString;
76
+ }, z.core.$loose>;
77
+ requestFingerprint: z.ZodString;
78
+ work: z.ZodObject<{
79
+ consumerPluginId: z.ZodString;
80
+ kind: z.ZodString;
81
+ attribution: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
82
+ }, z.core.$loose>;
83
+ state: z.ZodEnum<{
84
+ "not-invoked": "not-invoked";
85
+ "effect-possible": "effect-possible";
86
+ succeeded: "succeeded";
87
+ failed: "failed";
88
+ uncertain: "uncertain";
89
+ }>;
90
+ invoked: z.ZodBoolean;
91
+ source: z.ZodObject<{
92
+ ownerPluginId: z.ZodString;
93
+ kind: z.ZodLiteral<"plugin-setting">;
94
+ field: z.ZodString;
95
+ }, z.core.$loose>;
96
+ createdAt: z.ZodNumber;
97
+ settledAt: z.ZodNullable<z.ZodNumber>;
98
+ latencyMs: z.ZodNullable<z.ZodNumber>;
99
+ httpStatus: z.ZodNullable<z.ZodNumber>;
100
+ response: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
101
+ usage: z.ZodDiscriminatedUnion<[z.ZodObject<{
102
+ status: z.ZodLiteral<"reported">;
103
+ inputTokens: z.ZodNumber;
104
+ outputTokens: z.ZodNumber;
105
+ cost: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
106
+ }, z.core.$loose>, z.ZodObject<{
107
+ status: z.ZodEnum<{
108
+ missing: "missing";
109
+ invalid: "invalid";
110
+ }>;
111
+ cost: z.ZodType<Json, unknown, z.core.$ZodTypeInternals<Json, unknown>>;
112
+ }, z.core.$loose>], "status">;
113
+ error: z.ZodNullable<z.ZodObject<{
114
+ code: z.ZodString;
115
+ message: z.ZodString;
116
+ }, z.core.$loose>>;
117
+ }, z.core.$loose>;
118
+ export type OperationRecord = z.infer<typeof RecordSchema>;
119
+ export declare const DescribeEnvelopeSchema: z.ZodObject<{
120
+ protocol: z.ZodString;
121
+ versions: z.ZodArray<z.ZodNumber>;
122
+ }, z.core.$loose>;
123
+ export declare const DescribeSchema: z.ZodObject<{
124
+ protocol: z.ZodLiteral<"bb-jev">;
125
+ versions: z.ZodArray<z.ZodNumber>;
126
+ ownerPluginId: z.ZodString;
127
+ configured: z.ZodBoolean;
128
+ source: z.ZodObject<{
129
+ ownerPluginId: z.ZodString;
130
+ kind: z.ZodLiteral<"plugin-setting">;
131
+ field: z.ZodString;
132
+ }, z.core.$loose>;
133
+ workKinds: z.ZodArray<z.ZodString>;
134
+ limits: z.ZodObject<{
135
+ requestBytes: z.ZodNumber;
136
+ responseBytes: z.ZodNumber;
137
+ deadlineMs: z.ZodNumber;
138
+ concurrency: z.ZodNumber;
139
+ }, z.core.$loose>;
140
+ }, z.core.$loose>;
141
+ export type Description = z.infer<typeof DescribeSchema>;
142
+ export declare function decode<T>(schema: z.ZodType<T>, value: unknown, maxBytes?: number): T;
143
+ /** Bind every response to the requested operation; immutable request identity cannot drift on replay. */
144
+ export declare function decodeRecord(input: Submit | {
145
+ operation: OperationKey;
146
+ }, value: unknown): Promise<OperationRecord | null>;
@@ -0,0 +1,9 @@
1
+ import { type Submit, type OperationRecord } from './protocol.js';
2
+ export interface ConformancePort {
3
+ submit(input: Submit): Promise<OperationRecord>;
4
+ lookup(operation: Submit['operation']): Promise<OperationRecord | null>;
5
+ }
6
+ /** Real adopter callers supply an inert transport. The kit makes no inference itself. */
7
+ export declare function runJevConformance(port: ConformancePort, input: Submit): Promise<{
8
+ cases: string[];
9
+ }>;
package/package.json CHANGED
@@ -1,6 +1,58 @@
1
1
  {
2
2
  "name": "@phosphorco/bb-jev",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-alpha.1",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "Bounded shared JEV transport operations for independently installed BB plugins.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/phosphorco/bb-community-plugins.git",
10
+ "directory": "packages/bb-jev"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/types/protocol.d.ts",
15
+ "import": "./dist/protocol.js"
16
+ },
17
+ "./bb": {
18
+ "types": "./dist/types/bb.d.ts",
19
+ "import": "./dist/bb.js"
20
+ },
21
+ "./testing": {
22
+ "types": "./dist/types/testing.d.ts",
23
+ "import": "./dist/testing.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist/",
28
+ "README.md",
29
+ "CONTRACT.md",
30
+ "LICENSE"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "tag": "next"
35
+ },
36
+ "dependencies": {
37
+ "zod": "4.6.7"
38
+ },
39
+ "devDependencies": {
40
+ "bun": "1.3.14",
41
+ "typescript": "5.9.3",
42
+ "@get-bb/plugin-sdk": "0.6.52"
43
+ },
44
+ "scripts": {
45
+ "build": "bun tools/build.mjs && tsc -p tsconfig.declarations.json",
46
+ "typecheck": "tsc --noEmit",
47
+ "test": "npm run build && node --test test/*.test.mjs",
48
+ "test:packed-consumer": "node tools/packed-consumer.mjs"
49
+ },
50
+ "peerDependencies": {
51
+ "@get-bb/plugin-sdk": ">=0.6.52 <0.7.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@get-bb/plugin-sdk": {
55
+ "optional": true
56
+ }
57
+ }
58
+ }