@fabricorg/experience-runtime 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,131 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 337e89c: Make offline replay truthful and give three conformance suites teeth.
8
+
9
+ Offline replay carried no marker and dropped the capture moment. An adapter had to remember to set
10
+ `executionReason` for the gateway to apply offline eligibility rules, and capture-authority evidence
11
+ was stamped at receipt, so it attested a moment nobody was there for. `ExperienceReplayRequest` now
12
+ carries `executionReason` and `capturedAt` structurally, and the gateway uses the supplied instant,
13
+ bounded so it must parse and must not be in the future.
14
+
15
+ Replay resolved to `completed` or `failed`, which collapsed three outcomes the platform deliberately
16
+ distinguishes and kept every failure queued. A capture the platform had already applied counted as a
17
+ failure and retried, when replaying an idempotent action twice is the mechanism working; a capture
18
+ refused on authority grounds retried until its TTL rather than surfacing. `replay()` now returns
19
+ `reconciled` alongside the counts, holding each settled refusal and the reason given. Unrecognised
20
+ errors stay transient, so an unfamiliar failure is retried rather than discarded.
21
+
22
+ Gateway conformance accepted any result carrying an `actionInvocationId`, so a replacement that
23
+ fabricated an id and queried data directly passed; the subject now reports what the governed hosts
24
+ observed, and reads are certified through `ProjectionHost`. Component-pack certification accepted one
25
+ placeholder for every declared component and could not see a fresh function per call, because every
26
+ function stringifies to `undefined`. Renderers must now be distinct per component, undeclared names
27
+ must resolve to nothing, and determinism compares identity for anything not JSON-representable.
28
+
29
+ - 9a5b18a: Close what a fifth review found, including a regression the previous fix introduced.
30
+
31
+ **Ambiguity fired on legal documents.** Counting every declared view made publishing a view at two
32
+ versions a hard failure, and view versioning is a first-class pattern: the platform and the usage
33
+ contract both key a view on `name@version`. It also failed a release over a capability that release
34
+ never touched. Ambiguity is now reported for _granted_ references only, where a versionless reference
35
+ genuinely cannot pick between two versions, and a contract supplied twice gets its own finding
36
+ instead of making every one of its views look ambiguous.
37
+
38
+ **A failed replay is `unknown`, not `refused`.** `failed` is reachable after adapter steps succeeded
39
+ and the domain transaction committed, so telling an operator the action was definitely refused could
40
+ be as wrong as telling them it was applied. Only policy blocks and validation failures are decided
41
+ before any effect. A replay adapter that returns nothing is surfaced rather than counted as applied,
42
+ which was the one line the previous rewrite left untouched.
43
+
44
+ **`parked` and `expired` carry their records**, and reconciliation carries the invocation id. Bare
45
+ counts left nobody able to say which action needs approving or whose work was dropped, and external
46
+ reconciliation needs the id the type exists to enable.
47
+
48
+ Settled-failure classification gained the superseded-action-version case and dropped two it had
49
+ wrong: a future-dated capture comes good as clocks correct, and a gateway missing its replay window
50
+ is a deployment mistake somebody will fix. Discarding either would destroy work.
51
+
52
+ **`assertPortSuiteHasTeeth` takes a set of broken adapters.** Requiring one stub to trip every check
53
+ was unsatisfiable for any suite with a fixture-liveness guard, since the guard bites on the
54
+ complement of what it guards, and two reference ports could not pass their own suites. Port ids now
55
+ permit a hyphen in the first segment, so a port can be defined for every name a manifest can declare,
56
+ and `certifyAdapter` validates its definition rather than issuing evidence the gate reports as
57
+ missing.
58
+
59
+ - 9b2e55b: Close what a fourth review found: each earlier fix closed one instance of a class and left its
60
+ siblings.
61
+
62
+ **Replay disposition is now exhaustive over `ActionStatus`.** The previous fix enumerated the two
63
+ statuses that looked like refusals, so `failed`, `validation_failed` and `waiting_for_approval` fell
64
+ through and were deleted from the queue and reported to the operator as applied — the same bug the
65
+ fix set out to kill, one layer in. Any returned outcome now means the platform holds the invocation,
66
+ so the capture leaves the queue either way; what differs is whether a person needs to hear about it.
67
+ An undeterminable adapter outcome is reported as `unknown` rather than `refused`, because the effect
68
+ may well have happened. `isSettledReplayFailure` learned the refusals the sibling commit introduced,
69
+ including a capture rejected for age, which can never come good.
70
+
71
+ **A replay without `capturedAt` is refused.** It was reachable only through an untyped cast, so
72
+ omitting it skipped the age check entirely and stamped receipt time into durable capture evidence.
73
+
74
+ **A capability can name a namespaced port.** `definePort` required a dot and the manifest validator
75
+ forbade one, so no real capability could name a defined port and the certification gate was
76
+ unreachable. Certification is also checked structurally rather than by the presence of a property, so
77
+ a hand-written claim no longer satisfies the gate that exists to demand evidence, and a port
78
+ requirement narrows its candidates as each constraint is applied.
79
+
80
+ **`assertPortSuiteHasTeeth` names every vacuous check** rather than returning on the first one that
81
+ bites, and a reference port is actually run through it. The payments control asserts its result, so
82
+ an adapter that never charges no longer certifies. The capability-reference grammar matches the
83
+ platform's own view-name rule instead of being stricter than it, and ambiguity is counted by
84
+ occurrence across views and intents rather than by comparing names within one contract.
85
+
86
+ - 0d1f6f6: Close what a third independent review found, including two defects introduced by the previous fix.
87
+
88
+ **Offline replay counted refusals as successes.** `PlatformHost` reports a refused capture by
89
+ _returning_ `reconciliation_required` with the reason, not by throwing, so the queue took its success
90
+ branch, deleted the capture and reported it applied. The reconciliation path was unreachable. Replay
91
+ now reads the returned outcome. The already-applied branch is removed entirely: a repeated key
92
+ returns the original invocation without complaint, and an idempotency _conflict_ means the key was
93
+ reused for a materially different command, so that capture was never applied and is kept rather than
94
+ discarded.
95
+
96
+ **`deriveAuthorizationGrants` defaulted open and reached the signed artifact.** Requirements are
97
+ optional metadata, so a capability declaring none granted everything to an actor holding nothing.
98
+ Grants are also carried onto a release unexamined and promotion signs a route for each, so a surplus
99
+ grant became a signed route to a capability the screen never renders. Undeclared requirements now
100
+ withhold by default, and `document` scopes the result to references the screen actually binds.
101
+
102
+ Several conformance checks could not fail: a search suite passed an adapter returning nothing, a
103
+ payments suite passed a double-charger that omitted a provider reference, an identity suite certified
104
+ timestamps the scope check then rejected on every request, an MFE probe could be refused for an
105
+ unrelated reason, and a channel check accepted any exception as a refusal. Each now proves what it
106
+ claims. Promotion configuration rejects anything reading like a key rather than two spellings,
107
+ rejects unknown fields, and constrains the application name that decides the default output path.
108
+
109
+ ### Patch Changes
110
+
111
+ - Updated dependencies [8ce5e06]
112
+ - Updated dependencies [a598fb9]
113
+ - Updated dependencies [8f75ac9]
114
+ - Updated dependencies [9a5b18a]
115
+ - Updated dependencies [9b2e55b]
116
+ - Updated dependencies [0d1f6f6]
117
+ - Updated dependencies [274d948]
118
+ - @fabricorg/sdui-release@0.5.0
119
+
120
+ ## 0.1.0
121
+
122
+ ### Minor Changes
123
+
124
+ - Add signed channel and release admission, scoped render plans, fragment-event
125
+ sessions, governed view resolution, public conformance, and encrypted-store-required
126
+ offline capture contracts.
127
+
128
+ ### Patch Changes
129
+
130
+ - Updated dependencies
131
+ - @fabricorg/sdui-release@0.4.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fabric
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 ADDED
@@ -0,0 +1,20 @@
1
+ # `@fabricorg/experience-runtime`
2
+
3
+ The supported experience composition runtime, split by trust:
4
+
5
+ - `/server` verifies admitted releases and issues scope-bound render plans.
6
+ - `/client` consumes plans through fragment events and named bindings.
7
+ - `/offline` captures eligible events for governed replay.
8
+ - the root export contains serializable contracts only.
9
+
10
+ Client checks detect accidents. They are not authorization. Server plan resolution,
11
+ ProjectionHost, and PlatformHost remain authoritative.
12
+
13
+ Offline capture stores no credentials or identity claims and requires an
14
+ encrypted-at-rest adapter in production. A capture expires no later than its scoped
15
+ plan, so applications that support a longer offline window must issue an explicitly
16
+ bounded offline-capable plan. Replay always returns through the gateway and current
17
+ Host authority rules.
18
+
19
+ Replacement runtimes can run `experienceServerChecks` and replacement clients can run
20
+ `experienceClientChecks` without importing Fabric internals.
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // client.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ createExperienceSession: () => createExperienceSession,
24
+ experienceClientChecks: () => experienceClientChecks,
25
+ runExperienceClientChecks: () => runExperienceClientChecks
26
+ });
27
+ module.exports = __toCommonJS(client_exports);
28
+ function createExperienceSession(plan, gateway) {
29
+ const fragments = indexFragments(plan.root);
30
+ return {
31
+ plan,
32
+ async invoke(fragmentId, eventName, parameters, idempotencyKey) {
33
+ const fragment = fragments.get(fragmentId);
34
+ if (!fragment?.events?.includes(eventName)) {
35
+ throw new Error(`Fragment event "${fragmentId}.${eventName}" is not in the render plan.`);
36
+ }
37
+ return gateway.invoke({
38
+ planId: plan.planId,
39
+ fragmentId,
40
+ eventName,
41
+ parameters,
42
+ ...idempotencyKey === void 0 ? {} : { idempotencyKey }
43
+ });
44
+ },
45
+ async read(fragmentId, bindingName, parameters) {
46
+ const fragment = fragments.get(fragmentId);
47
+ if (!fragment?.bindings?.includes(bindingName)) {
48
+ throw new Error(`Fragment binding "${fragmentId}.${bindingName}" is not in the render plan.`);
49
+ }
50
+ return gateway.read({
51
+ planId: plan.planId,
52
+ fragmentId,
53
+ bindingName,
54
+ ...parameters === void 0 ? {} : { parameters }
55
+ });
56
+ }
57
+ };
58
+ }
59
+ function experienceClientChecks() {
60
+ const plan = {
61
+ formatVersion: 1,
62
+ planId: "conformance-plan",
63
+ application: "conformance",
64
+ channel: "test",
65
+ releaseDigest: "a".repeat(64),
66
+ assemblyDigest: "b".repeat(64),
67
+ treeId: "root",
68
+ tokenSet: { name: "conformance", version: "1.0.0" },
69
+ expiresAt: "2099-01-01T00:00:00.000Z",
70
+ root: { id: "root", component: "Action", events: ["press"], bindings: ["summary"] }
71
+ };
72
+ return [
73
+ {
74
+ id: "fabric.experience.client-tuple-routing.v1",
75
+ async run(subject) {
76
+ const calls = [];
77
+ const session = subject.create(plan, {
78
+ async invoke(request) {
79
+ calls.push(`${request.fragmentId}.${request.eventName}`);
80
+ return { status: "accepted" };
81
+ },
82
+ async read(request) {
83
+ calls.push(`${request.fragmentId}.${request.bindingName}`);
84
+ return { data: { ok: true } };
85
+ }
86
+ });
87
+ await session.invoke("root", "press", {});
88
+ await session.read("root", "summary");
89
+ if (calls.join(",") !== "root.press,root.summary") throw new Error("Client did not route through fragment-event and fragment-binding tuples.");
90
+ return calls;
91
+ }
92
+ },
93
+ {
94
+ id: "fabric.experience.client-plan-denial.v1",
95
+ async run(subject) {
96
+ let called = false;
97
+ const session = subject.create(plan, {
98
+ async invoke() {
99
+ called = true;
100
+ return { status: "accepted" };
101
+ },
102
+ async read() {
103
+ called = true;
104
+ return { data: null };
105
+ }
106
+ });
107
+ let rejected = false;
108
+ try {
109
+ await session.invoke("root", "undeclared", {});
110
+ } catch {
111
+ rejected = true;
112
+ }
113
+ if (!rejected || called) throw new Error("Client forwarded an event that was absent from the scoped plan.");
114
+ return ["undeclared event rejected locally"];
115
+ }
116
+ }
117
+ ];
118
+ }
119
+ async function runExperienceClientChecks(subject, checks = experienceClientChecks()) {
120
+ const results = [];
121
+ for (const check of checks) {
122
+ try {
123
+ results.push({ id: check.id, status: "passed", evidence: await check.run(subject) });
124
+ } catch (error) {
125
+ results.push({ id: check.id, status: "failed", evidence: [], error: error instanceof Error ? error.message : String(error) });
126
+ }
127
+ }
128
+ return { passed: results.every((result) => result.status === "passed"), checks: results };
129
+ }
130
+ function indexFragments(root) {
131
+ const fragments = /* @__PURE__ */ new Map();
132
+ const visit = (fragment) => {
133
+ if (fragments.has(fragment.id)) throw new Error(`Render plan repeats fragment "${fragment.id}".`);
134
+ fragments.set(fragment.id, fragment);
135
+ for (const child of fragment.children ?? []) visit(child);
136
+ };
137
+ visit(root);
138
+ return fragments;
139
+ }
140
+ // Annotate the CommonJS export names for ESM import in node:
141
+ 0 && (module.exports = {
142
+ createExperienceSession,
143
+ experienceClientChecks,
144
+ runExperienceClientChecks
145
+ });
146
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../client.ts"],"sourcesContent":["import type {\n\tExperienceGatewayClient,\n\tExperienceSession,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport type {\n\tExperienceGatewayClient,\n\tExperienceIntentRequest,\n\tExperienceReadRequest,\n\tExperienceSession,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport function createExperienceSession(\n\tplan: ScopedRenderPlan,\n\tgateway: ExperienceGatewayClient,\n): ExperienceSession {\n\tconst fragments = indexFragments(plan.root);\n\treturn {\n\t\tplan,\n\t\tasync invoke(fragmentId, eventName, parameters, idempotencyKey) {\n\t\t\tconst fragment = fragments.get(fragmentId);\n\t\t\tif (!fragment?.events?.includes(eventName)) {\n\t\t\t\tthrow new Error(`Fragment event \"${fragmentId}.${eventName}\" is not in the render plan.`);\n\t\t\t}\n\t\t\treturn gateway.invoke({\n\t\t\t\tplanId: plan.planId,\n\t\t\t\tfragmentId,\n\t\t\t\teventName,\n\t\t\t\tparameters,\n\t\t\t\t...(idempotencyKey === undefined ? {} : { idempotencyKey }),\n\t\t\t});\n\t\t},\n\t\tasync read(fragmentId, bindingName, parameters) {\n\t\t\tconst fragment = fragments.get(fragmentId);\n\t\t\tif (!fragment?.bindings?.includes(bindingName)) {\n\t\t\t\tthrow new Error(`Fragment binding \"${fragmentId}.${bindingName}\" is not in the render plan.`);\n\t\t\t}\n\t\t\treturn gateway.read({\n\t\t\t\tplanId: plan.planId,\n\t\t\t\tfragmentId,\n\t\t\t\tbindingName,\n\t\t\t\t...(parameters === undefined ? {} : { parameters }),\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface ExperienceClientConformanceSubject {\n\tcreate(plan: ScopedRenderPlan, gateway: ExperienceGatewayClient): ExperienceSession;\n}\n\nexport interface ExperienceClientCheck {\n\tid: \"fabric.experience.client-tuple-routing.v1\" | \"fabric.experience.client-plan-denial.v1\";\n\trun(subject: ExperienceClientConformanceSubject): Promise<string[]>;\n}\n\nexport function experienceClientChecks(): readonly ExperienceClientCheck[] {\n\tconst plan: ScopedRenderPlan = {\n\t\tformatVersion: 1,\n\t\tplanId: \"conformance-plan\",\n\t\tapplication: \"conformance\",\n\t\tchannel: \"test\",\n\t\treleaseDigest: \"a\".repeat(64),\n\t\tassemblyDigest: \"b\".repeat(64),\n\t\ttreeId: \"root\",\n\t\ttokenSet: { name: \"conformance\", version: \"1.0.0\" },\n\t\texpiresAt: \"2099-01-01T00:00:00.000Z\",\n\t\troot: { id: \"root\", component: \"Action\", events: [\"press\"], bindings: [\"summary\"] },\n\t};\n\treturn [\n\t\t{\n\t\t\tid: \"fabric.experience.client-tuple-routing.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst calls: string[] = [];\n\t\t\t\tconst session = subject.create(plan, {\n\t\t\t\t\tasync invoke(request) { calls.push(`${request.fragmentId}.${request.eventName}`); return { status: \"accepted\" }; },\n\t\t\t\t\tasync read(request) { calls.push(`${request.fragmentId}.${request.bindingName}`); return { data: { ok: true } }; },\n\t\t\t\t});\n\t\t\t\tawait session.invoke(\"root\", \"press\", {});\n\t\t\t\tawait session.read(\"root\", \"summary\");\n\t\t\t\tif (calls.join(\",\") !== \"root.press,root.summary\") throw new Error(\"Client did not route through fragment-event and fragment-binding tuples.\");\n\t\t\t\treturn calls;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.experience.client-plan-denial.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tlet called = false;\n\t\t\t\tconst session = subject.create(plan, {\n\t\t\t\t\tasync invoke() { called = true; return { status: \"accepted\" }; },\n\t\t\t\t\tasync read() { called = true; return { data: null }; },\n\t\t\t\t});\n\t\t\t\tlet rejected = false;\n\t\t\t\ttry { await session.invoke(\"root\", \"undeclared\", {}); } catch { rejected = true; }\n\t\t\t\tif (!rejected || called) throw new Error(\"Client forwarded an event that was absent from the scoped plan.\");\n\t\t\t\treturn [\"undeclared event rejected locally\"];\n\t\t\t},\n\t\t},\n\t];\n}\n\nexport async function runExperienceClientChecks(\n\tsubject: ExperienceClientConformanceSubject,\n\tchecks: readonly ExperienceClientCheck[] = experienceClientChecks(),\n): Promise<{ passed: boolean; checks: Array<{ id: ExperienceClientCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> }> {\n\tconst results: Array<{ id: ExperienceClientCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> = [];\n\tfor (const check of checks) {\n\t\ttry { results.push({ id: check.id, status: \"passed\", evidence: await check.run(subject) }); }\n\t\tcatch (error) { results.push({ id: check.id, status: \"failed\", evidence: [], error: error instanceof Error ? error.message : String(error) }); }\n\t}\n\treturn { passed: results.every((result) => result.status === \"passed\"), checks: results };\n}\n\nfunction indexFragments(root: RenderPlanFragment): Map<string, RenderPlanFragment> {\n\tconst fragments = new Map<string, RenderPlanFragment>();\n\tconst visit = (fragment: RenderPlanFragment): void => {\n\t\tif (fragments.has(fragment.id)) throw new Error(`Render plan repeats fragment \"${fragment.id}\".`);\n\t\tfragments.set(fragment.id, fragment);\n\t\tfor (const child of fragment.children ?? []) visit(child);\n\t};\n\tvisit(root);\n\treturn fragments;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,23 @@
1
+ import { ScopedRenderPlan, ExperienceGatewayClient, ExperienceSession } from './contracts.cjs';
2
+ export { ExperienceIntentRequest, ExperienceReadRequest, RenderPlanFragment } from './contracts.cjs';
3
+
4
+ declare function createExperienceSession(plan: ScopedRenderPlan, gateway: ExperienceGatewayClient): ExperienceSession;
5
+ interface ExperienceClientConformanceSubject {
6
+ create(plan: ScopedRenderPlan, gateway: ExperienceGatewayClient): ExperienceSession;
7
+ }
8
+ interface ExperienceClientCheck {
9
+ id: "fabric.experience.client-tuple-routing.v1" | "fabric.experience.client-plan-denial.v1";
10
+ run(subject: ExperienceClientConformanceSubject): Promise<string[]>;
11
+ }
12
+ declare function experienceClientChecks(): readonly ExperienceClientCheck[];
13
+ declare function runExperienceClientChecks(subject: ExperienceClientConformanceSubject, checks?: readonly ExperienceClientCheck[]): Promise<{
14
+ passed: boolean;
15
+ checks: Array<{
16
+ id: ExperienceClientCheck["id"];
17
+ status: "passed" | "failed";
18
+ evidence: string[];
19
+ error?: string;
20
+ }>;
21
+ }>;
22
+
23
+ export { type ExperienceClientCheck, type ExperienceClientConformanceSubject, ExperienceGatewayClient, ExperienceSession, ScopedRenderPlan, createExperienceSession, experienceClientChecks, runExperienceClientChecks };
@@ -0,0 +1,23 @@
1
+ import { ScopedRenderPlan, ExperienceGatewayClient, ExperienceSession } from './contracts.js';
2
+ export { ExperienceIntentRequest, ExperienceReadRequest, RenderPlanFragment } from './contracts.js';
3
+
4
+ declare function createExperienceSession(plan: ScopedRenderPlan, gateway: ExperienceGatewayClient): ExperienceSession;
5
+ interface ExperienceClientConformanceSubject {
6
+ create(plan: ScopedRenderPlan, gateway: ExperienceGatewayClient): ExperienceSession;
7
+ }
8
+ interface ExperienceClientCheck {
9
+ id: "fabric.experience.client-tuple-routing.v1" | "fabric.experience.client-plan-denial.v1";
10
+ run(subject: ExperienceClientConformanceSubject): Promise<string[]>;
11
+ }
12
+ declare function experienceClientChecks(): readonly ExperienceClientCheck[];
13
+ declare function runExperienceClientChecks(subject: ExperienceClientConformanceSubject, checks?: readonly ExperienceClientCheck[]): Promise<{
14
+ passed: boolean;
15
+ checks: Array<{
16
+ id: ExperienceClientCheck["id"];
17
+ status: "passed" | "failed";
18
+ evidence: string[];
19
+ error?: string;
20
+ }>;
21
+ }>;
22
+
23
+ export { type ExperienceClientCheck, type ExperienceClientConformanceSubject, ExperienceGatewayClient, ExperienceSession, ScopedRenderPlan, createExperienceSession, experienceClientChecks, runExperienceClientChecks };
package/dist/client.js ADDED
@@ -0,0 +1,119 @@
1
+ // client.ts
2
+ function createExperienceSession(plan, gateway) {
3
+ const fragments = indexFragments(plan.root);
4
+ return {
5
+ plan,
6
+ async invoke(fragmentId, eventName, parameters, idempotencyKey) {
7
+ const fragment = fragments.get(fragmentId);
8
+ if (!fragment?.events?.includes(eventName)) {
9
+ throw new Error(`Fragment event "${fragmentId}.${eventName}" is not in the render plan.`);
10
+ }
11
+ return gateway.invoke({
12
+ planId: plan.planId,
13
+ fragmentId,
14
+ eventName,
15
+ parameters,
16
+ ...idempotencyKey === void 0 ? {} : { idempotencyKey }
17
+ });
18
+ },
19
+ async read(fragmentId, bindingName, parameters) {
20
+ const fragment = fragments.get(fragmentId);
21
+ if (!fragment?.bindings?.includes(bindingName)) {
22
+ throw new Error(`Fragment binding "${fragmentId}.${bindingName}" is not in the render plan.`);
23
+ }
24
+ return gateway.read({
25
+ planId: plan.planId,
26
+ fragmentId,
27
+ bindingName,
28
+ ...parameters === void 0 ? {} : { parameters }
29
+ });
30
+ }
31
+ };
32
+ }
33
+ function experienceClientChecks() {
34
+ const plan = {
35
+ formatVersion: 1,
36
+ planId: "conformance-plan",
37
+ application: "conformance",
38
+ channel: "test",
39
+ releaseDigest: "a".repeat(64),
40
+ assemblyDigest: "b".repeat(64),
41
+ treeId: "root",
42
+ tokenSet: { name: "conformance", version: "1.0.0" },
43
+ expiresAt: "2099-01-01T00:00:00.000Z",
44
+ root: { id: "root", component: "Action", events: ["press"], bindings: ["summary"] }
45
+ };
46
+ return [
47
+ {
48
+ id: "fabric.experience.client-tuple-routing.v1",
49
+ async run(subject) {
50
+ const calls = [];
51
+ const session = subject.create(plan, {
52
+ async invoke(request) {
53
+ calls.push(`${request.fragmentId}.${request.eventName}`);
54
+ return { status: "accepted" };
55
+ },
56
+ async read(request) {
57
+ calls.push(`${request.fragmentId}.${request.bindingName}`);
58
+ return { data: { ok: true } };
59
+ }
60
+ });
61
+ await session.invoke("root", "press", {});
62
+ await session.read("root", "summary");
63
+ if (calls.join(",") !== "root.press,root.summary") throw new Error("Client did not route through fragment-event and fragment-binding tuples.");
64
+ return calls;
65
+ }
66
+ },
67
+ {
68
+ id: "fabric.experience.client-plan-denial.v1",
69
+ async run(subject) {
70
+ let called = false;
71
+ const session = subject.create(plan, {
72
+ async invoke() {
73
+ called = true;
74
+ return { status: "accepted" };
75
+ },
76
+ async read() {
77
+ called = true;
78
+ return { data: null };
79
+ }
80
+ });
81
+ let rejected = false;
82
+ try {
83
+ await session.invoke("root", "undeclared", {});
84
+ } catch {
85
+ rejected = true;
86
+ }
87
+ if (!rejected || called) throw new Error("Client forwarded an event that was absent from the scoped plan.");
88
+ return ["undeclared event rejected locally"];
89
+ }
90
+ }
91
+ ];
92
+ }
93
+ async function runExperienceClientChecks(subject, checks = experienceClientChecks()) {
94
+ const results = [];
95
+ for (const check of checks) {
96
+ try {
97
+ results.push({ id: check.id, status: "passed", evidence: await check.run(subject) });
98
+ } catch (error) {
99
+ results.push({ id: check.id, status: "failed", evidence: [], error: error instanceof Error ? error.message : String(error) });
100
+ }
101
+ }
102
+ return { passed: results.every((result) => result.status === "passed"), checks: results };
103
+ }
104
+ function indexFragments(root) {
105
+ const fragments = /* @__PURE__ */ new Map();
106
+ const visit = (fragment) => {
107
+ if (fragments.has(fragment.id)) throw new Error(`Render plan repeats fragment "${fragment.id}".`);
108
+ fragments.set(fragment.id, fragment);
109
+ for (const child of fragment.children ?? []) visit(child);
110
+ };
111
+ visit(root);
112
+ return fragments;
113
+ }
114
+ export {
115
+ createExperienceSession,
116
+ experienceClientChecks,
117
+ runExperienceClientChecks
118
+ };
119
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../client.ts"],"sourcesContent":["import type {\n\tExperienceGatewayClient,\n\tExperienceSession,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport type {\n\tExperienceGatewayClient,\n\tExperienceIntentRequest,\n\tExperienceReadRequest,\n\tExperienceSession,\n\tRenderPlanFragment,\n\tScopedRenderPlan,\n} from \"./contracts\";\n\nexport function createExperienceSession(\n\tplan: ScopedRenderPlan,\n\tgateway: ExperienceGatewayClient,\n): ExperienceSession {\n\tconst fragments = indexFragments(plan.root);\n\treturn {\n\t\tplan,\n\t\tasync invoke(fragmentId, eventName, parameters, idempotencyKey) {\n\t\t\tconst fragment = fragments.get(fragmentId);\n\t\t\tif (!fragment?.events?.includes(eventName)) {\n\t\t\t\tthrow new Error(`Fragment event \"${fragmentId}.${eventName}\" is not in the render plan.`);\n\t\t\t}\n\t\t\treturn gateway.invoke({\n\t\t\t\tplanId: plan.planId,\n\t\t\t\tfragmentId,\n\t\t\t\teventName,\n\t\t\t\tparameters,\n\t\t\t\t...(idempotencyKey === undefined ? {} : { idempotencyKey }),\n\t\t\t});\n\t\t},\n\t\tasync read(fragmentId, bindingName, parameters) {\n\t\t\tconst fragment = fragments.get(fragmentId);\n\t\t\tif (!fragment?.bindings?.includes(bindingName)) {\n\t\t\t\tthrow new Error(`Fragment binding \"${fragmentId}.${bindingName}\" is not in the render plan.`);\n\t\t\t}\n\t\t\treturn gateway.read({\n\t\t\t\tplanId: plan.planId,\n\t\t\t\tfragmentId,\n\t\t\t\tbindingName,\n\t\t\t\t...(parameters === undefined ? {} : { parameters }),\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport interface ExperienceClientConformanceSubject {\n\tcreate(plan: ScopedRenderPlan, gateway: ExperienceGatewayClient): ExperienceSession;\n}\n\nexport interface ExperienceClientCheck {\n\tid: \"fabric.experience.client-tuple-routing.v1\" | \"fabric.experience.client-plan-denial.v1\";\n\trun(subject: ExperienceClientConformanceSubject): Promise<string[]>;\n}\n\nexport function experienceClientChecks(): readonly ExperienceClientCheck[] {\n\tconst plan: ScopedRenderPlan = {\n\t\tformatVersion: 1,\n\t\tplanId: \"conformance-plan\",\n\t\tapplication: \"conformance\",\n\t\tchannel: \"test\",\n\t\treleaseDigest: \"a\".repeat(64),\n\t\tassemblyDigest: \"b\".repeat(64),\n\t\ttreeId: \"root\",\n\t\ttokenSet: { name: \"conformance\", version: \"1.0.0\" },\n\t\texpiresAt: \"2099-01-01T00:00:00.000Z\",\n\t\troot: { id: \"root\", component: \"Action\", events: [\"press\"], bindings: [\"summary\"] },\n\t};\n\treturn [\n\t\t{\n\t\t\tid: \"fabric.experience.client-tuple-routing.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tconst calls: string[] = [];\n\t\t\t\tconst session = subject.create(plan, {\n\t\t\t\t\tasync invoke(request) { calls.push(`${request.fragmentId}.${request.eventName}`); return { status: \"accepted\" }; },\n\t\t\t\t\tasync read(request) { calls.push(`${request.fragmentId}.${request.bindingName}`); return { data: { ok: true } }; },\n\t\t\t\t});\n\t\t\t\tawait session.invoke(\"root\", \"press\", {});\n\t\t\t\tawait session.read(\"root\", \"summary\");\n\t\t\t\tif (calls.join(\",\") !== \"root.press,root.summary\") throw new Error(\"Client did not route through fragment-event and fragment-binding tuples.\");\n\t\t\t\treturn calls;\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tid: \"fabric.experience.client-plan-denial.v1\",\n\t\t\tasync run(subject) {\n\t\t\t\tlet called = false;\n\t\t\t\tconst session = subject.create(plan, {\n\t\t\t\t\tasync invoke() { called = true; return { status: \"accepted\" }; },\n\t\t\t\t\tasync read() { called = true; return { data: null }; },\n\t\t\t\t});\n\t\t\t\tlet rejected = false;\n\t\t\t\ttry { await session.invoke(\"root\", \"undeclared\", {}); } catch { rejected = true; }\n\t\t\t\tif (!rejected || called) throw new Error(\"Client forwarded an event that was absent from the scoped plan.\");\n\t\t\t\treturn [\"undeclared event rejected locally\"];\n\t\t\t},\n\t\t},\n\t];\n}\n\nexport async function runExperienceClientChecks(\n\tsubject: ExperienceClientConformanceSubject,\n\tchecks: readonly ExperienceClientCheck[] = experienceClientChecks(),\n): Promise<{ passed: boolean; checks: Array<{ id: ExperienceClientCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> }> {\n\tconst results: Array<{ id: ExperienceClientCheck[\"id\"]; status: \"passed\" | \"failed\"; evidence: string[]; error?: string }> = [];\n\tfor (const check of checks) {\n\t\ttry { results.push({ id: check.id, status: \"passed\", evidence: await check.run(subject) }); }\n\t\tcatch (error) { results.push({ id: check.id, status: \"failed\", evidence: [], error: error instanceof Error ? error.message : String(error) }); }\n\t}\n\treturn { passed: results.every((result) => result.status === \"passed\"), checks: results };\n}\n\nfunction indexFragments(root: RenderPlanFragment): Map<string, RenderPlanFragment> {\n\tconst fragments = new Map<string, RenderPlanFragment>();\n\tconst visit = (fragment: RenderPlanFragment): void => {\n\t\tif (fragments.has(fragment.id)) throw new Error(`Render plan repeats fragment \"${fragment.id}\".`);\n\t\tfragments.set(fragment.id, fragment);\n\t\tfor (const child of fragment.children ?? []) visit(child);\n\t};\n\tvisit(root);\n\treturn fragments;\n}\n"],"mappings":";AAgBO,SAAS,wBACf,MACA,SACoB;AACpB,QAAM,YAAY,eAAe,KAAK,IAAI;AAC1C,SAAO;AAAA,IACN;AAAA,IACA,MAAM,OAAO,YAAY,WAAW,YAAY,gBAAgB;AAC/D,YAAM,WAAW,UAAU,IAAI,UAAU;AACzC,UAAI,CAAC,UAAU,QAAQ,SAAS,SAAS,GAAG;AAC3C,cAAM,IAAI,MAAM,mBAAmB,UAAU,IAAI,SAAS,8BAA8B;AAAA,MACzF;AACA,aAAO,QAAQ,OAAO;AAAA,QACrB,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,mBAAmB,SAAY,CAAC,IAAI,EAAE,eAAe;AAAA,MAC1D,CAAC;AAAA,IACF;AAAA,IACA,MAAM,KAAK,YAAY,aAAa,YAAY;AAC/C,YAAM,WAAW,UAAU,IAAI,UAAU;AACzC,UAAI,CAAC,UAAU,UAAU,SAAS,WAAW,GAAG;AAC/C,cAAM,IAAI,MAAM,qBAAqB,UAAU,IAAI,WAAW,8BAA8B;AAAA,MAC7F;AACA,aAAO,QAAQ,KAAK;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,QACA,GAAI,eAAe,SAAY,CAAC,IAAI,EAAE,WAAW;AAAA,MAClD,CAAC;AAAA,IACF;AAAA,EACD;AACD;AAWO,SAAS,yBAA2D;AAC1E,QAAM,OAAyB;AAAA,IAC9B,eAAe;AAAA,IACf,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,SAAS;AAAA,IACT,eAAe,IAAI,OAAO,EAAE;AAAA,IAC5B,gBAAgB,IAAI,OAAO,EAAE;AAAA,IAC7B,QAAQ;AAAA,IACR,UAAU,EAAE,MAAM,eAAe,SAAS,QAAQ;AAAA,IAClD,WAAW;AAAA,IACX,MAAM,EAAE,IAAI,QAAQ,WAAW,UAAU,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,SAAS,EAAE;AAAA,EACnF;AACA,SAAO;AAAA,IACN;AAAA,MACC,IAAI;AAAA,MACJ,MAAM,IAAI,SAAS;AAClB,cAAM,QAAkB,CAAC;AACzB,cAAM,UAAU,QAAQ,OAAO,MAAM;AAAA,UACpC,MAAM,OAAO,SAAS;AAAE,kBAAM,KAAK,GAAG,QAAQ,UAAU,IAAI,QAAQ,SAAS,EAAE;AAAG,mBAAO,EAAE,QAAQ,WAAW;AAAA,UAAG;AAAA,UACjH,MAAM,KAAK,SAAS;AAAE,kBAAM,KAAK,GAAG,QAAQ,UAAU,IAAI,QAAQ,WAAW,EAAE;AAAG,mBAAO,EAAE,MAAM,EAAE,IAAI,KAAK,EAAE;AAAA,UAAG;AAAA,QAClH,CAAC;AACD,cAAM,QAAQ,OAAO,QAAQ,SAAS,CAAC,CAAC;AACxC,cAAM,QAAQ,KAAK,QAAQ,SAAS;AACpC,YAAI,MAAM,KAAK,GAAG,MAAM,0BAA2B,OAAM,IAAI,MAAM,0EAA0E;AAC7I,eAAO;AAAA,MACR;AAAA,IACD;AAAA,IACA;AAAA,MACC,IAAI;AAAA,MACJ,MAAM,IAAI,SAAS;AAClB,YAAI,SAAS;AACb,cAAM,UAAU,QAAQ,OAAO,MAAM;AAAA,UACpC,MAAM,SAAS;AAAE,qBAAS;AAAM,mBAAO,EAAE,QAAQ,WAAW;AAAA,UAAG;AAAA,UAC/D,MAAM,OAAO;AAAE,qBAAS;AAAM,mBAAO,EAAE,MAAM,KAAK;AAAA,UAAG;AAAA,QACtD,CAAC;AACD,YAAI,WAAW;AACf,YAAI;AAAE,gBAAM,QAAQ,OAAO,QAAQ,cAAc,CAAC,CAAC;AAAA,QAAG,QAAQ;AAAE,qBAAW;AAAA,QAAM;AACjF,YAAI,CAAC,YAAY,OAAQ,OAAM,IAAI,MAAM,iEAAiE;AAC1G,eAAO,CAAC,mCAAmC;AAAA,MAC5C;AAAA,IACD;AAAA,EACD;AACD;AAEA,eAAsB,0BACrB,SACA,SAA2C,uBAAuB,GACkF;AACpJ,QAAM,UAAuH,CAAC;AAC9H,aAAW,SAAS,QAAQ;AAC3B,QAAI;AAAE,cAAQ,KAAK,EAAE,IAAI,MAAM,IAAI,QAAQ,UAAU,UAAU,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;AAAA,IAAG,SACrF,OAAO;AAAE,cAAQ,KAAK,EAAE,IAAI,MAAM,IAAI,QAAQ,UAAU,UAAU,CAAC,GAAG,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,IAAG;AAAA,EAChJ;AACA,SAAO,EAAE,QAAQ,QAAQ,MAAM,CAAC,WAAW,OAAO,WAAW,QAAQ,GAAG,QAAQ,QAAQ;AACzF;AAEA,SAAS,eAAe,MAA2D;AAClF,QAAM,YAAY,oBAAI,IAAgC;AACtD,QAAM,QAAQ,CAAC,aAAuC;AACrD,QAAI,UAAU,IAAI,SAAS,EAAE,EAAG,OAAM,IAAI,MAAM,iCAAiC,SAAS,EAAE,IAAI;AAChG,cAAU,IAAI,SAAS,IAAI,QAAQ;AACnC,eAAW,SAAS,SAAS,YAAY,CAAC,EAAG,OAAM,KAAK;AAAA,EACzD;AACA,QAAM,IAAI;AACV,SAAO;AACR;","names":[]}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // contracts.ts
17
+ var contracts_exports = {};
18
+ module.exports = __toCommonJS(contracts_exports);
19
+ //# sourceMappingURL=contracts.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../contracts.ts"],"sourcesContent":["export interface ExperienceScope {\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: string;\n}\n\nexport interface RenderPlanFragment {\n\tid: string;\n\tcomponent?: string;\n\tprops?: Record<string, unknown>;\n\tslot?: string;\n\tevents?: readonly string[];\n\tofflineEvents?: readonly string[];\n\tbindings?: readonly string[];\n\tchildren?: readonly RenderPlanFragment[];\n}\n\nexport interface ScopedRenderPlan {\n\tformatVersion: 1;\n\tplanId: string;\n\tapplication: string;\n\tchannel: string;\n\treleaseDigest: string;\n\tassemblyDigest: string;\n\ttreeId: string;\n\troot: RenderPlanFragment;\n\ttokenSet: { name: string; version: string };\n\texpiresAt: string;\n}\n\nexport interface ExperienceIntentRequest {\n\tplanId: string;\n\tfragmentId: string;\n\teventName: string;\n\tparameters: Record<string, unknown>;\n\tidempotencyKey?: string;\n}\n\n/**\n * A replay of an action captured while disconnected.\n *\n * The marker and the capture moment travel with the request rather than by\n * adapter convention. An adapter that forwarded a replay as an ordinary intent\n * would skip the Host's offline eligibility and reconciliation rules, and\n * capture-authority evidence stamped at receipt cannot truthfully attest when\n * the operator actually acted.\n */\nexport interface ExperienceReplayRequest extends ExperienceIntentRequest {\n\texecutionReason: \"offline_replay\";\n\t/** RFC 3339 instant the action was captured on the device. */\n\tcapturedAt: string;\n\tidempotencyKey: string;\n}\n\nexport interface ExperienceReadRequest {\n\tplanId: string;\n\tfragmentId: string;\n\tbindingName: string;\n\tparameters?: Record<string, unknown>;\n}\n\nexport interface ExperienceGatewayClient {\n\tinvoke(request: ExperienceIntentRequest): Promise<unknown>;\n\treplay?(request: ExperienceReplayRequest): Promise<ExperienceInvocationOutcome>;\n\tread(request: ExperienceReadRequest): Promise<{ data: unknown; stale?: boolean }>;\n}\n\n/**\n * What a governed submission comes back as.\n *\n * The Host reports a refusal by *returning* `reconciliation_required` with the\n * reason, not by throwing: a lapsed authority is a business outcome rather than\n * an error. Anything reading only the exception path will count a refusal as a\n * success.\n */\nexport interface ExperienceInvocationOutcome {\n\tstatus?: string;\n\treconciliation?: { kind?: string; message?: string };\n\t/** Present when an adapter's own outcome could not be determined. */\n\tadapterReconciliation?: unknown;\n\t[key: string]: unknown;\n}\n\nexport interface ExperienceSession {\n\treadonly plan: ScopedRenderPlan;\n\tinvoke(fragmentId: string, eventName: string, parameters: Record<string, unknown>, idempotencyKey?: string): Promise<unknown>;\n\tread(fragmentId: string, bindingName: string, parameters?: Record<string, unknown>): Promise<{ data: unknown; stale?: boolean }>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -0,0 +1,95 @@
1
+ interface ExperienceScope {
2
+ tenantId: string;
3
+ spaceId: string;
4
+ actorId: string;
5
+ actorType: string;
6
+ }
7
+ interface RenderPlanFragment {
8
+ id: string;
9
+ component?: string;
10
+ props?: Record<string, unknown>;
11
+ slot?: string;
12
+ events?: readonly string[];
13
+ offlineEvents?: readonly string[];
14
+ bindings?: readonly string[];
15
+ children?: readonly RenderPlanFragment[];
16
+ }
17
+ interface ScopedRenderPlan {
18
+ formatVersion: 1;
19
+ planId: string;
20
+ application: string;
21
+ channel: string;
22
+ releaseDigest: string;
23
+ assemblyDigest: string;
24
+ treeId: string;
25
+ root: RenderPlanFragment;
26
+ tokenSet: {
27
+ name: string;
28
+ version: string;
29
+ };
30
+ expiresAt: string;
31
+ }
32
+ interface ExperienceIntentRequest {
33
+ planId: string;
34
+ fragmentId: string;
35
+ eventName: string;
36
+ parameters: Record<string, unknown>;
37
+ idempotencyKey?: string;
38
+ }
39
+ /**
40
+ * A replay of an action captured while disconnected.
41
+ *
42
+ * The marker and the capture moment travel with the request rather than by
43
+ * adapter convention. An adapter that forwarded a replay as an ordinary intent
44
+ * would skip the Host's offline eligibility and reconciliation rules, and
45
+ * capture-authority evidence stamped at receipt cannot truthfully attest when
46
+ * the operator actually acted.
47
+ */
48
+ interface ExperienceReplayRequest extends ExperienceIntentRequest {
49
+ executionReason: "offline_replay";
50
+ /** RFC 3339 instant the action was captured on the device. */
51
+ capturedAt: string;
52
+ idempotencyKey: string;
53
+ }
54
+ interface ExperienceReadRequest {
55
+ planId: string;
56
+ fragmentId: string;
57
+ bindingName: string;
58
+ parameters?: Record<string, unknown>;
59
+ }
60
+ interface ExperienceGatewayClient {
61
+ invoke(request: ExperienceIntentRequest): Promise<unknown>;
62
+ replay?(request: ExperienceReplayRequest): Promise<ExperienceInvocationOutcome>;
63
+ read(request: ExperienceReadRequest): Promise<{
64
+ data: unknown;
65
+ stale?: boolean;
66
+ }>;
67
+ }
68
+ /**
69
+ * What a governed submission comes back as.
70
+ *
71
+ * The Host reports a refusal by *returning* `reconciliation_required` with the
72
+ * reason, not by throwing: a lapsed authority is a business outcome rather than
73
+ * an error. Anything reading only the exception path will count a refusal as a
74
+ * success.
75
+ */
76
+ interface ExperienceInvocationOutcome {
77
+ status?: string;
78
+ reconciliation?: {
79
+ kind?: string;
80
+ message?: string;
81
+ };
82
+ /** Present when an adapter's own outcome could not be determined. */
83
+ adapterReconciliation?: unknown;
84
+ [key: string]: unknown;
85
+ }
86
+ interface ExperienceSession {
87
+ readonly plan: ScopedRenderPlan;
88
+ invoke(fragmentId: string, eventName: string, parameters: Record<string, unknown>, idempotencyKey?: string): Promise<unknown>;
89
+ read(fragmentId: string, bindingName: string, parameters?: Record<string, unknown>): Promise<{
90
+ data: unknown;
91
+ stale?: boolean;
92
+ }>;
93
+ }
94
+
95
+ export type { ExperienceGatewayClient, ExperienceIntentRequest, ExperienceInvocationOutcome, ExperienceReadRequest, ExperienceReplayRequest, ExperienceScope, ExperienceSession, RenderPlanFragment, ScopedRenderPlan };