@exponential_app/engine 0.0.0-stage → 0.1.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/LICENSE +201 -0
- package/NOTICE +54 -0
- package/README.md +35 -2
- package/dist/capabilities.js +29 -0
- package/dist/capabilities.js.map +9 -0
- package/dist/chunks/index-3nf9f7pv.js +315 -0
- package/dist/chunks/index-3nf9f7pv.js.map +10 -0
- package/dist/chunks/index-3rgr16n2.js +443 -0
- package/dist/chunks/index-3rgr16n2.js.map +9 -0
- package/dist/chunks/index-79swfbsm.js +366 -0
- package/dist/chunks/index-79swfbsm.js.map +10 -0
- package/dist/chunks/index-knntt4cb.js +259 -0
- package/dist/chunks/index-knntt4cb.js.map +10 -0
- package/dist/chunks/index-m2845dn6.js +72 -0
- package/dist/chunks/index-m2845dn6.js.map +10 -0
- package/dist/chunks/index-pqpnz4ns.js +2213 -0
- package/dist/chunks/index-pqpnz4ns.js.map +9 -0
- package/dist/chunks/index-q4zws02e.js +2301 -0
- package/dist/chunks/index-q4zws02e.js.map +10 -0
- package/dist/chunks/index-s3xjr701.js +170 -0
- package/dist/chunks/index-s3xjr701.js.map +10 -0
- package/dist/chunks/index-sk8b0dh4.js +126 -0
- package/dist/chunks/index-sk8b0dh4.js.map +10 -0
- package/dist/chunks/index-tvsykgcv.js +3015 -0
- package/dist/chunks/index-tvsykgcv.js.map +19 -0
- package/dist/chunks/index-v0m7r4y3.js +6869 -0
- package/dist/chunks/index-v0m7r4y3.js.map +13 -0
- package/dist/consent.js +24 -0
- package/dist/consent.js.map +9 -0
- package/dist/index.js +654 -0
- package/dist/index.js.map +12 -0
- package/dist/lifecycle.js +11 -0
- package/dist/lifecycle.js.map +9 -0
- package/dist/outbox.js +27 -0
- package/dist/outbox.js.map +9 -0
- package/dist/pdf.js +15 -0
- package/dist/pdf.js.map +9 -0
- package/dist/providers.js +26 -0
- package/dist/providers.js.map +9 -0
- package/dist/runner.js +24 -0
- package/dist/runner.js.map +9 -0
- package/dist/tools.js +38 -0
- package/dist/tools.js.map +9 -0
- package/dist/types/domain-contract/src/app-records.d.ts +3148 -0
- package/dist/types/domain-contract/src/app-share.d.ts +222 -0
- package/dist/types/domain-contract/src/apps.d.ts +612 -0
- package/dist/types/eengine/fixtures/providers.json +55 -0
- package/dist/types/eengine/src/broker.d.ts +80 -0
- package/dist/types/eengine/src/capabilities.d.ts +88 -0
- package/dist/types/eengine/src/consent.d.ts +50 -0
- package/dist/types/eengine/src/index.d.ts +12 -0
- package/dist/types/eengine/src/instance.d.ts +131 -0
- package/dist/types/eengine/src/lifecycle.d.ts +64 -0
- package/dist/types/eengine/src/outbox.d.ts +3 -0
- package/dist/types/eengine/src/pdf.d.ts +36 -0
- package/dist/types/eengine/src/providers.d.ts +38 -0
- package/dist/types/eengine/src/runner.d.ts +82 -0
- package/dist/types/eengine/src/stores.d.ts +125 -0
- package/dist/types/eengine/src/synced.d.ts +59 -0
- package/dist/types/eengine/src/tools.d.ts +79 -0
- package/dist/types/eengine/src/web/index.d.ts +20 -0
- package/dist/types/eengine/src/web/mcp.d.ts +27 -0
- package/dist/types/eengine/src/web/platform.d.ts +91 -0
- package/dist/types/eengine/src/web/stores.d.ts +125 -0
- package/dist/types/emcp/fixtures/automations.json +521 -0
- package/dist/types/emcp/fixtures/hub.json +498 -0
- package/dist/types/emcp/src/access.d.ts +55 -0
- package/dist/types/emcp/src/audit.d.ts +22 -0
- package/dist/types/emcp/src/automations.d.ts +111 -0
- package/dist/types/emcp/src/exposes.d.ts +242 -0
- package/dist/types/emcp/src/hub.d.ts +63 -0
- package/dist/types/emcp/src/index.d.ts +7 -0
- package/dist/types/emcp/src/pure.d.ts +11 -0
- package/dist/types/emcp/src/upstream.d.ts +288 -0
- package/dist/types/emcp/src/vault.d.ts +34 -0
- package/dist/web/index.js +791 -0
- package/dist/web/index.js.map +13 -0
- package/fixtures/bundle.json +1165 -0
- package/fixtures/capabilities.json +434 -0
- package/fixtures/consent.json +146 -0
- package/fixtures/lifecycle.json +125 -0
- package/fixtures/outbox.json +22 -0
- package/fixtures/providers.json +55 -0
- package/fixtures/runner.json +2167 -0
- package/package.json +92 -4
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalJson,
|
|
3
|
+
scopeDef2
|
|
4
|
+
} from "./index-tvsykgcv.js";
|
|
5
|
+
// packages/eengine/fixtures/consent.json
|
|
6
|
+
var consent_default = {
|
|
7
|
+
$comment: "VAPP-260 (Engine E0, epic VAPP-259, docs/spec/apps.md §13.2): CONSENT. The package's capability manifest (format 5 `capabilities: [{id, why, origins?}]`) is the CEILING; the person's decision comes next (capabilities.json `brokerDecide`); the audit row last. GRANTS are per app: `{<scope id>: granted | denied}` (absent = undecided), kept ONLY for `ask` scopes (a `picker` scope's consent is the platform picker, every call). THE SHEET (`sheetCases`, `consentSheet`): when the broker answers `ask`, the Engine shows ONE genUI surface built from existing components only (Drawer + Text + ChoiceTiles + Button; no new primitive), the same on every platform and the default `ConsentUi` (a host may override the painter, never the rule): a bottom Drawer titled `Allow {app} to {verb}?` (`copy.title`; the scope's `verb`, an `origins` scope's `{origins}` = its origins joined with `, `), the manifest's `why` as a Text, ChoiceTiles `answer` (one column) with `copy.options` in order (`allow`, `once`, `deny`) starting on `allow`, and a Button `copy.confirm` (the default variant) whose press sends the event `consent.answer` with the picked value; closing the Drawer (Escape, the backdrop, a swipe) answers `dismiss`. THE ANSWER (`answerCases`, `consentAnswer`): `allow` = the call goes ahead and the grant is stored `granted`; `once` = the call goes ahead and nothing is stored (the next call asks again); `deny` = the call is refused `consentDenied` and `denied` is stored; `dismiss` = refused `consentDismissed`, nothing stored. MANAGE (`manageCases`, `manageSheet`): the app's Permissions surface (its … menu, the runner's app menu) = a Drawer `copy.manageTitle` with one Switch per DECLARED `ask` scope in manifest order (label = the capability's `label`, description = the `why`, checked = granted; event `consent.toggle` with the scope) and no Switch for `picker` scopes; flipping one is the gesture: on = `granted`, off = REVOKE = the grant removed (undecided, so the next call asks). WRITES (`writeCases`, `grantWrite`): a grant changes ONLY through the ConsentUi after a user gesture (`via: consentUi`, `gesture: true`); anything else (a hub tool, an agent, the package, the host config, a sync) is refused `onlyConsentUi`, a ConsentUi write without a gesture `noGesture`, an undeclared or `picker` or `hostOnly` scope `notAsk`. PERSISTENCE (`storeCases`, `grantsRecord` / `grantsOnPackage`): the Vault (emcp `Vault`: Keychain, Keystore, libsecret, the server's sealed column) holds key `vaultKey` with `{app}` = the package id, value = canonical JSON `{grants, origins, signer, v}`: `v` = `recordVersion`, `signer` = the package signature's public key, `origins` = the `http.fetch` origins the grant was given for (sorted). On every package load the Engine reconciles (`grantsOnPackage`): another signer = a different author, every grant dropped; a scope the new version no longer declares (or now declares as non-ask) is dropped; an `http.fetch` grant whose declared origins are no longer a subset of the granted ones is dropped (new origins ask again); an unreadable record or another `v` = no grants (ask again, never fail open).",
|
|
8
|
+
recordVersion: 1,
|
|
9
|
+
vaultKey: "eengine/grants/{app}",
|
|
10
|
+
copy: {
|
|
11
|
+
title: "Allow {app} to {verb}?",
|
|
12
|
+
options: [
|
|
13
|
+
{ label: "Allow", value: "allow" },
|
|
14
|
+
{ label: "Allow once", value: "once" },
|
|
15
|
+
{ label: "Don't allow", value: "deny" }
|
|
16
|
+
],
|
|
17
|
+
confirm: "Continue",
|
|
18
|
+
manageTitle: "Permissions"
|
|
19
|
+
},
|
|
20
|
+
answerCases: [
|
|
21
|
+
{ scope: "photos.read", grants: {}, answer: "allow", expected: { verdict: "call", reason: null, grants: { "photos.read": "granted" } } },
|
|
22
|
+
{ scope: "photos.read", grants: {}, answer: "once", expected: { verdict: "call", reason: null, grants: {} } },
|
|
23
|
+
{ scope: "photos.read", grants: { "camera.capture": "granted" }, answer: "deny", expected: { verdict: "denied", reason: "consentDenied", grants: { "camera.capture": "granted", "photos.read": "denied" } } },
|
|
24
|
+
{ scope: "photos.read", grants: {}, answer: "dismiss", expected: { verdict: "denied", reason: "consentDismissed", grants: {} } }
|
|
25
|
+
],
|
|
26
|
+
writeCases: [
|
|
27
|
+
{ name: "a switch flipped on grants", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "photos.read", value: "granted" }, expected: { grants: { "photos.read": "granted" }, refused: null } },
|
|
28
|
+
{ name: "a switch flipped off revokes", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: { "photos.read": "granted" }, write: { via: "consentUi", gesture: true, scope: "photos.read", value: null }, expected: { grants: {}, refused: null } },
|
|
29
|
+
{ name: "no gesture, no change", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "consentUi", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "noGesture" } },
|
|
30
|
+
{ name: "an agent cannot grant itself", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "agent", gesture: true, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
|
|
31
|
+
{ name: "a hub tool cannot grant", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "hubTool", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
|
|
32
|
+
{ name: "the package cannot grant", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "package", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
|
|
33
|
+
{ name: "the host config cannot grant", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "hostConfig", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
|
|
34
|
+
{ name: "an undeclared scope cannot be granted", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "camera.capture", value: "granted" }, expected: { grants: {}, refused: "notAsk" } },
|
|
35
|
+
{ name: "a picker scope keeps no grant", manifest: [{ id: "share.send", why: "Share an album." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "share.send", value: "granted" }, expected: { grants: {}, refused: "notAsk" } },
|
|
36
|
+
{ name: "a host-only scope is never granted", manifest: [{ id: "shell.edit", why: "Rearrange the panel." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "shell.edit", value: "granted" }, expected: { grants: {}, refused: "notAsk" } }
|
|
37
|
+
],
|
|
38
|
+
storeCases: [
|
|
39
|
+
{
|
|
40
|
+
name: "the record round-trips as canonical JSON",
|
|
41
|
+
app: "at.exponential.photobook",
|
|
42
|
+
signer: "pk1",
|
|
43
|
+
manifest: [{ id: "photos.read", why: "Pick the photos." }, { id: "http.fetch", why: "Rates.", origins: ["https://b.example.com", "https://a.example.com"] }],
|
|
44
|
+
grants: { "photos.read": "granted", "http.fetch": "granted" },
|
|
45
|
+
expected: {
|
|
46
|
+
key: "eengine/grants/at.exponential.photobook",
|
|
47
|
+
value: '{"grants":{"http.fetch":"granted","photos.read":"granted"},"origins":["https://a.example.com","https://b.example.com"],"signer":"pk1","v":1}'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
reconcileCases: [
|
|
52
|
+
{
|
|
53
|
+
name: "the same signer keeps what is still declared",
|
|
54
|
+
stored: '{"grants":{"camera.capture":"granted","photos.read":"denied"},"origins":[],"signer":"pk1","v":1}',
|
|
55
|
+
signer: "pk1",
|
|
56
|
+
manifest: [{ id: "photos.read", why: "Pick the photos." }],
|
|
57
|
+
expected: { "photos.read": "denied" }
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "another signer drops every grant",
|
|
61
|
+
stored: '{"grants":{"photos.read":"granted"},"origins":[],"signer":"pk1","v":1}',
|
|
62
|
+
signer: "pk2",
|
|
63
|
+
manifest: [{ id: "photos.read", why: "Pick the photos." }],
|
|
64
|
+
expected: {}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "new origins ask again",
|
|
68
|
+
stored: '{"grants":{"http.fetch":"granted"},"origins":["https://a.example.com"],"signer":"pk1","v":1}',
|
|
69
|
+
signer: "pk1",
|
|
70
|
+
manifest: [{ id: "http.fetch", why: "Rates.", origins: ["https://a.example.com", "https://b.example.com"] }],
|
|
71
|
+
expected: {}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "fewer origins keep the grant",
|
|
75
|
+
stored: '{"grants":{"http.fetch":"granted"},"origins":["https://a.example.com","https://b.example.com"],"signer":"pk1","v":1}',
|
|
76
|
+
signer: "pk1",
|
|
77
|
+
manifest: [{ id: "http.fetch", why: "Rates.", origins: ["https://b.example.com"] }],
|
|
78
|
+
expected: { "http.fetch": "granted" }
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "a scope that became a picker scope is dropped",
|
|
82
|
+
stored: '{"grants":{"share.send":"granted"},"origins":[],"signer":"pk1","v":1}',
|
|
83
|
+
signer: "pk1",
|
|
84
|
+
manifest: [{ id: "share.send", why: "Share." }],
|
|
85
|
+
expected: {}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "another record version asks again",
|
|
89
|
+
stored: '{"grants":{"photos.read":"granted"},"origins":[],"signer":"pk1","v":2}',
|
|
90
|
+
signer: "pk1",
|
|
91
|
+
manifest: [{ id: "photos.read", why: "Pick the photos." }],
|
|
92
|
+
expected: {}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "an unreadable record asks again",
|
|
96
|
+
stored: "not json",
|
|
97
|
+
signer: "pk1",
|
|
98
|
+
manifest: [{ id: "photos.read", why: "Pick the photos." }],
|
|
99
|
+
expected: {}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "no record yet",
|
|
103
|
+
stored: null,
|
|
104
|
+
signer: "pk1",
|
|
105
|
+
manifest: [{ id: "photos.read", why: "Pick the photos." }],
|
|
106
|
+
expected: {}
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
sheetCases: [
|
|
110
|
+
{
|
|
111
|
+
name: "the photos sheet",
|
|
112
|
+
app: "Photobook",
|
|
113
|
+
entry: { id: "photos.read", why: "Pick the photos for an album." },
|
|
114
|
+
expected: [
|
|
115
|
+
{ id: "root", component: "Drawer", title: "Allow Photobook to access your photos?", open: true, side: "bottom", children: ["why", "answer"], slots: { footer: "confirm" }, on: { change: { event: { name: "consent.answer", context: { answer: "dismiss" } } } } },
|
|
116
|
+
{ id: "why", component: "Text", text: "Pick the photos for an album." },
|
|
117
|
+
{ id: "answer", component: "ChoiceTiles", name: "answer", columns: 1, value: { "@path": "/answer" }, options: [{ label: "Allow", value: "allow" }, { label: "Allow once", value: "once" }, { label: "Don't allow", value: "deny" }] },
|
|
118
|
+
{ id: "confirm", component: "Button", label: "Continue", on: { press: { event: { name: "consent.answer", context: { answer: { "@path": "/answer" } } } } } }
|
|
119
|
+
],
|
|
120
|
+
data: { answer: "allow" }
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "an origins scope names its origins",
|
|
124
|
+
app: "Rates",
|
|
125
|
+
entry: { id: "http.fetch", why: "Load exchange rates.", origins: ["https://api.example.com", "https://backup.example.com"] },
|
|
126
|
+
expected: [
|
|
127
|
+
{ id: "root", component: "Drawer", title: "Allow Rates to connect to https://api.example.com, https://backup.example.com?", open: true, side: "bottom", children: ["why", "answer"], slots: { footer: "confirm" }, on: { change: { event: { name: "consent.answer", context: { answer: "dismiss" } } } } },
|
|
128
|
+
{ id: "why", component: "Text", text: "Load exchange rates." },
|
|
129
|
+
{ id: "answer", component: "ChoiceTiles", name: "answer", columns: 1, value: { "@path": "/answer" }, options: [{ label: "Allow", value: "allow" }, { label: "Allow once", value: "once" }, { label: "Don't allow", value: "deny" }] },
|
|
130
|
+
{ id: "confirm", component: "Button", label: "Continue", on: { press: { event: { name: "consent.answer", context: { answer: { "@path": "/answer" } } } } } }
|
|
131
|
+
],
|
|
132
|
+
data: { answer: "allow" }
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
manageCases: [
|
|
136
|
+
{
|
|
137
|
+
name: "one switch per declared ask scope",
|
|
138
|
+
manifest: [
|
|
139
|
+
{ id: "photos.read", why: "Pick the photos for an album." },
|
|
140
|
+
{ id: "share.send", why: "Share a finished album." },
|
|
141
|
+
{ id: "notifications.post", why: "Remind you of due pages." }
|
|
142
|
+
],
|
|
143
|
+
grants: { "photos.read": "granted", "notifications.post": "denied" },
|
|
144
|
+
expected: [
|
|
145
|
+
{ id: "root", component: "Drawer", title: "Permissions", open: true, side: "bottom", children: ["scope_0", "scope_1"] },
|
|
146
|
+
{ id: "scope_0", component: "Switch", label: "Photos", description: "Pick the photos for an album.", checked: true, on: { change: { event: { name: "consent.toggle", context: { scope: "photos.read" } } } } },
|
|
147
|
+
{ id: "scope_1", component: "Switch", label: "Notifications", description: "Remind you of due pages.", checked: false, on: { change: { event: { name: "consent.toggle", context: { scope: "notifications.post" } } } } }
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// packages/eengine/src/consent.ts
|
|
154
|
+
var CONSENT_COPY2 = consent_default.copy;
|
|
155
|
+
var GRANTS_RECORD_VERSION2 = consent_default.recordVersion;
|
|
156
|
+
var sortGrants = (grants) => Object.fromEntries(Object.entries(grants).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
157
|
+
function consentAnswer2(grants, scope, answer) {
|
|
158
|
+
if (answer === `allow`)
|
|
159
|
+
return { verdict: `call`, reason: null, grants: sortGrants({ ...grants, [scope]: `granted` }) };
|
|
160
|
+
if (answer === `once`)
|
|
161
|
+
return { verdict: `call`, reason: null, grants: sortGrants({ ...grants }) };
|
|
162
|
+
if (answer === `deny`)
|
|
163
|
+
return { verdict: `denied`, reason: `consentDenied`, grants: sortGrants({ ...grants, [scope]: `denied` }) };
|
|
164
|
+
return { verdict: `denied`, reason: `consentDismissed`, grants: sortGrants({ ...grants }) };
|
|
165
|
+
}
|
|
166
|
+
function declaredAsk(manifest, id) {
|
|
167
|
+
const def = scopeDef2(id);
|
|
168
|
+
return !!def && !def.scope.hostOnly && def.scope.consent === `ask` && manifest.some((m) => m.id === id);
|
|
169
|
+
}
|
|
170
|
+
function grantWrite2(manifest, grants, write) {
|
|
171
|
+
if (write.via !== `consentUi`)
|
|
172
|
+
return { grants, refused: `onlyConsentUi` };
|
|
173
|
+
if (!write.gesture)
|
|
174
|
+
return { grants, refused: `noGesture` };
|
|
175
|
+
if (!declaredAsk(manifest, write.scope))
|
|
176
|
+
return { grants, refused: `notAsk` };
|
|
177
|
+
const next = { ...grants };
|
|
178
|
+
if (write.value === null)
|
|
179
|
+
delete next[write.scope];
|
|
180
|
+
else
|
|
181
|
+
next[write.scope] = write.value;
|
|
182
|
+
return { grants: sortGrants(next), refused: null };
|
|
183
|
+
}
|
|
184
|
+
var httpOrigins = (manifest) => [...manifest.find((m) => m.id === `http.fetch`)?.origins ?? []].sort();
|
|
185
|
+
function grantsRecord2(app, signer, manifest, grants) {
|
|
186
|
+
return {
|
|
187
|
+
key: consent_default.vaultKey.replace(`{app}`, app),
|
|
188
|
+
value: canonicalJson({ grants: sortGrants(grants), origins: httpOrigins(manifest), signer, v: GRANTS_RECORD_VERSION2 })
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function grantsOnPackage2(stored, signer, manifest) {
|
|
192
|
+
if (stored === null)
|
|
193
|
+
return {};
|
|
194
|
+
let record;
|
|
195
|
+
try {
|
|
196
|
+
record = JSON.parse(stored);
|
|
197
|
+
} catch {
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
if (!record || typeof record !== `object`)
|
|
201
|
+
return {};
|
|
202
|
+
const r = record;
|
|
203
|
+
if (r.v !== GRANTS_RECORD_VERSION2 || r.signer !== signer || !r.grants || typeof r.grants !== `object`)
|
|
204
|
+
return {};
|
|
205
|
+
const granted = Array.isArray(r.origins) ? r.origins.filter((o) => typeof o === `string`) : [];
|
|
206
|
+
const out = {};
|
|
207
|
+
for (const [id, value] of Object.entries(r.grants)) {
|
|
208
|
+
if (value !== `granted` && value !== `denied`)
|
|
209
|
+
continue;
|
|
210
|
+
if (!declaredAsk(manifest, id))
|
|
211
|
+
continue;
|
|
212
|
+
if (scopeDef2(id)?.scope.origins && !httpOrigins(manifest).every((o) => granted.includes(o)))
|
|
213
|
+
continue;
|
|
214
|
+
out[id] = value;
|
|
215
|
+
}
|
|
216
|
+
return sortGrants(out);
|
|
217
|
+
}
|
|
218
|
+
function consentSheet2(app, entry) {
|
|
219
|
+
const def = scopeDef2(entry.id);
|
|
220
|
+
const verb = (def?.scope.verb ?? entry.id).replace(`{origins}`, (entry.origins ?? []).join(`, `));
|
|
221
|
+
const title = consent_default.copy.title.replace(`{app}`, app).replace(`{verb}`, verb);
|
|
222
|
+
return {
|
|
223
|
+
components: [
|
|
224
|
+
{
|
|
225
|
+
id: `root`,
|
|
226
|
+
component: `Drawer`,
|
|
227
|
+
title,
|
|
228
|
+
open: true,
|
|
229
|
+
side: `bottom`,
|
|
230
|
+
children: [`why`, `answer`],
|
|
231
|
+
slots: { footer: `confirm` },
|
|
232
|
+
on: { change: { event: { name: `consent.answer`, context: { answer: `dismiss` } } } }
|
|
233
|
+
},
|
|
234
|
+
{ id: `why`, component: `Text`, text: entry.why },
|
|
235
|
+
{ id: `answer`, component: `ChoiceTiles`, name: `answer`, columns: 1, value: { "@path": `/answer` }, options: consent_default.copy.options.map((o) => ({ ...o })) },
|
|
236
|
+
{ id: `confirm`, component: `Button`, label: consent_default.copy.confirm, on: { press: { event: { name: `consent.answer`, context: { answer: { "@path": `/answer` } } } } } }
|
|
237
|
+
],
|
|
238
|
+
data: { answer: `allow` }
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function manageSheet2(manifest, grants) {
|
|
242
|
+
const rows = manifest.filter((m) => declaredAsk(manifest, m.id));
|
|
243
|
+
return [
|
|
244
|
+
{ id: `root`, component: `Drawer`, title: consent_default.copy.manageTitle, open: true, side: `bottom`, children: rows.map((_, i) => `scope_${i}`) },
|
|
245
|
+
...rows.map((m, i) => ({
|
|
246
|
+
id: `scope_${i}`,
|
|
247
|
+
component: `Switch`,
|
|
248
|
+
label: scopeDef2(m.id).capability.label,
|
|
249
|
+
description: m.why,
|
|
250
|
+
checked: grants[m.id] === `granted`,
|
|
251
|
+
on: { change: { event: { name: `consent.toggle`, context: { scope: m.id } } } }
|
|
252
|
+
}))
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export { CONSENT_COPY2, GRANTS_RECORD_VERSION2, consentAnswer2, grantWrite2, grantsRecord2, grantsOnPackage2, consentSheet2, manageSheet2 };
|
|
257
|
+
|
|
258
|
+
//# debugId=45BA72763170257B64756E2164756E21
|
|
259
|
+
//# sourceMappingURL=index-knntt4cb.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../packages/eengine/src/consent.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// VAPP-260: consent. The answer to the sheet, who may write a grant, the\n// Vault record and its reconcile on every package load, and the two genUI\n// surfaces (the ask sheet, the Permissions sheet) built from existing\n// components only. Locked by `fixtures/consent.json`.\n\nimport { canonicalJson } from \"@exp/emcp\"\n\nimport fixture from \"../fixtures/consent.json\" with { type: \"json\" }\nimport { type BrokerVerdictKind, type Grants, type GrantValue, type ManifestEntry, scopeDef } from \"./capabilities\"\n\nexport type ConsentAnswer = `allow` | `once` | `deny` | `dismiss`\nexport type GrantVia = `consentUi` | `agent` | `hubTool` | `package` | `hostConfig` | `sync`\n\nexport const CONSENT_COPY = fixture.copy\nexport const GRANTS_RECORD_VERSION: number = fixture.recordVersion\n\n/** A genUI component (the flat A2UI list a surface carries). */\nexport type SurfaceComponent = Record<string, unknown> & { id: string; component: string }\n\nconst sortGrants = (grants: Grants): Grants => Object.fromEntries(Object.entries(grants).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) as Grants\n\n/** The sheet's answer → the call's verdict and the grants to store. */\nexport function consentAnswer(grants: Grants, scope: string, answer: ConsentAnswer): { verdict: BrokerVerdictKind; reason: string | null; grants: Grants } {\n if (answer === `allow`) return { verdict: `call`, reason: null, grants: sortGrants({ ...grants, [scope]: `granted` }) }\n if (answer === `once`) return { verdict: `call`, reason: null, grants: sortGrants({ ...grants }) }\n if (answer === `deny`) return { verdict: `denied`, reason: `consentDenied`, grants: sortGrants({ ...grants, [scope]: `denied` }) }\n return { verdict: `denied`, reason: `consentDismissed`, grants: sortGrants({ ...grants }) }\n}\n\n/** Is `id` a declared `ask` scope of the manifest (one a grant is kept for)? */\nfunction declaredAsk(manifest: readonly ManifestEntry[], id: string): boolean {\n const def = scopeDef(id)\n return !!def && !def.scope.hostOnly && def.scope.consent === `ask` && manifest.some((m) => m.id === id)\n}\n\n/** A grant write: only the ConsentUi, only after a gesture, only an ask scope. */\nexport function grantWrite(\n manifest: readonly ManifestEntry[],\n grants: Grants,\n write: { via: GrantVia; gesture: boolean; scope: string; value: GrantValue | null },\n): { grants: Grants; refused: string | null } {\n if (write.via !== `consentUi`) return { grants, refused: `onlyConsentUi` }\n if (!write.gesture) return { grants, refused: `noGesture` }\n if (!declaredAsk(manifest, write.scope)) return { grants, refused: `notAsk` }\n const next = { ...grants }\n if (write.value === null) delete next[write.scope]\n else next[write.scope] = write.value\n return { grants: sortGrants(next), refused: null }\n}\n\nconst httpOrigins = (manifest: readonly ManifestEntry[]): string[] => [...(manifest.find((m) => m.id === `http.fetch`)?.origins ?? [])].sort()\n\n/** The Vault key and canonical JSON value of an app's grants. */\nexport function grantsRecord(app: string, signer: string, manifest: readonly ManifestEntry[], grants: Grants): { key: string; value: string } {\n return {\n key: fixture.vaultKey.replace(`{app}`, app),\n value: canonicalJson({ grants: sortGrants(grants), origins: httpOrigins(manifest), signer, v: GRANTS_RECORD_VERSION }),\n }\n}\n\n/** The grants that survive a package load (`stored` = the Vault value, or null). */\nexport function grantsOnPackage(stored: string | null, signer: string, manifest: readonly ManifestEntry[]): Grants {\n if (stored === null) return {}\n let record: unknown\n try {\n record = JSON.parse(stored)\n } catch {\n return {}\n }\n if (!record || typeof record !== `object`) return {}\n const r = record as { v?: unknown; signer?: unknown; grants?: unknown; origins?: unknown }\n if (r.v !== GRANTS_RECORD_VERSION || r.signer !== signer || !r.grants || typeof r.grants !== `object`) return {}\n const granted = Array.isArray(r.origins) ? r.origins.filter((o): o is string => typeof o === `string`) : []\n const out: Grants = {}\n for (const [id, value] of Object.entries(r.grants as Record<string, unknown>)) {\n if (value !== `granted` && value !== `denied`) continue\n if (!declaredAsk(manifest, id)) continue\n if (scopeDef(id)?.scope.origins && !httpOrigins(manifest).every((o) => granted.includes(o))) continue\n out[id] = value\n }\n return sortGrants(out)\n}\n\n/** The ask sheet for one manifest entry: the surface's components and data. */\nexport function consentSheet(app: string, entry: ManifestEntry): { components: SurfaceComponent[]; data: { answer: ConsentAnswer } } {\n const def = scopeDef(entry.id)\n const verb = (def?.scope.verb ?? entry.id).replace(`{origins}`, (entry.origins ?? []).join(`, `))\n const title = fixture.copy.title.replace(`{app}`, app).replace(`{verb}`, verb)\n return {\n components: [\n {\n id: `root`,\n component: `Drawer`,\n title,\n open: true,\n side: `bottom`,\n children: [`why`, `answer`],\n slots: { footer: `confirm` },\n on: { change: { event: { name: `consent.answer`, context: { answer: `dismiss` } } } },\n },\n { id: `why`, component: `Text`, text: entry.why },\n { id: `answer`, component: `ChoiceTiles`, name: `answer`, columns: 1, value: { \"@path\": `/answer` }, options: fixture.copy.options.map((o) => ({ ...o })) },\n { id: `confirm`, component: `Button`, label: fixture.copy.confirm, on: { press: { event: { name: `consent.answer`, context: { answer: { \"@path\": `/answer` } } } } } },\n ],\n data: { answer: `allow` },\n }\n}\n\n/** The Permissions sheet: one Switch per declared ask scope, in manifest order. */\nexport function manageSheet(manifest: readonly ManifestEntry[], grants: Grants): SurfaceComponent[] {\n const rows = manifest.filter((m) => declaredAsk(manifest, m.id))\n return [\n { id: `root`, component: `Drawer`, title: fixture.copy.manageTitle, open: true, side: `bottom`, children: rows.map((_, i) => `scope_${i}`) },\n ...rows.map((m, i) => ({\n id: `scope_${i}`,\n component: `Switch`,\n label: scopeDef(m.id)!.capability.label,\n description: m.why,\n checked: grants[m.id] === `granted`,\n on: { change: { event: { name: `consent.toggle`, context: { scope: m.id } } } },\n })),\n ]\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "45BA72763170257B64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// packages/eengine/src/lifecycle.ts
|
|
2
|
+
import { crossAppTarget, resolveScreen } from "@exponential_app/eui/host";
|
|
3
|
+
var STOPPED2 = { phase: `stopped`, screen: null, params: null };
|
|
4
|
+
function plainTarget(cx) {
|
|
5
|
+
const last = cx.lastScreen;
|
|
6
|
+
const resolved = resolveScreen(cx.pkg, last?.screen ?? null, last?.params ?? null);
|
|
7
|
+
return resolved ? { screen: resolved.screen, params: resolved.params } : null;
|
|
8
|
+
}
|
|
9
|
+
function linkTarget(event, cx) {
|
|
10
|
+
if (event.type === `deepLink`)
|
|
11
|
+
return crossAppTarget(cx.pkg, event.screen ?? null, event.params ?? null);
|
|
12
|
+
if (event.type === `notificationTap`) {
|
|
13
|
+
if (event.app !== cx.pkg.id)
|
|
14
|
+
return { error: `otherApp` };
|
|
15
|
+
const resolved = resolveScreen(cx.pkg, event.screen ?? null, event.params ?? null);
|
|
16
|
+
return resolved ? { screen: resolved.screen, params: resolved.params } : { error: `no such screen` };
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
var ignored = (state) => ({ state, effects: [] });
|
|
21
|
+
function coldLaunch(target, effects) {
|
|
22
|
+
effects.push({ effect: `automations`, on: true });
|
|
23
|
+
if (target)
|
|
24
|
+
effects.push({ effect: `open`, screen: target.screen, params: target.params });
|
|
25
|
+
effects.push({ effect: `sources`, live: true }, { effect: `flush` });
|
|
26
|
+
return { state: { phase: `foreground`, screen: target?.screen ?? null, params: target?.params ?? null }, effects };
|
|
27
|
+
}
|
|
28
|
+
function lifecycleStep2(state, event, cx) {
|
|
29
|
+
const running = state.phase !== `stopped`;
|
|
30
|
+
const persist = () => state.screen !== null ? [{ effect: `persist`, screen: state.screen, params: { ...state.params ?? {} } }] : [];
|
|
31
|
+
switch (event.type) {
|
|
32
|
+
case `launch`:
|
|
33
|
+
return running ? ignored(state) : coldLaunch(plainTarget(cx), []);
|
|
34
|
+
case `foreground`:
|
|
35
|
+
if (state.phase !== `background`)
|
|
36
|
+
return ignored(state);
|
|
37
|
+
return { state: { ...state, phase: `foreground` }, effects: [{ effect: `sources`, live: true }, { effect: `flush` }] };
|
|
38
|
+
case `background`:
|
|
39
|
+
if (state.phase !== `foreground`)
|
|
40
|
+
return ignored(state);
|
|
41
|
+
return { state: { ...state, phase: `background` }, effects: [...persist(), { effect: `sources`, live: false }] };
|
|
42
|
+
case `terminate`:
|
|
43
|
+
if (!running)
|
|
44
|
+
return ignored(state);
|
|
45
|
+
return {
|
|
46
|
+
state: STOPPED2,
|
|
47
|
+
effects: [...persist(), ...state.phase === `foreground` ? [{ effect: `sources`, live: false }] : [], { effect: `automations`, on: false }]
|
|
48
|
+
};
|
|
49
|
+
case `online`:
|
|
50
|
+
return state.phase === `foreground` ? { state, effects: [{ effect: `flush` }] } : ignored(state);
|
|
51
|
+
case `deepLink`:
|
|
52
|
+
case `notificationTap`: {
|
|
53
|
+
const target = linkTarget(event, cx);
|
|
54
|
+
if (!target)
|
|
55
|
+
return ignored(state);
|
|
56
|
+
if (`error` in target) {
|
|
57
|
+
const refused = { effect: `refused`, reason: target.error };
|
|
58
|
+
return running ? { state, effects: [refused] } : coldLaunch(plainTarget(cx), [refused]);
|
|
59
|
+
}
|
|
60
|
+
if (!running)
|
|
61
|
+
return coldLaunch(target, []);
|
|
62
|
+
const open = { effect: `open`, screen: target.screen, params: target.params };
|
|
63
|
+
const next = { phase: `foreground`, screen: target.screen, params: target.params };
|
|
64
|
+
return { state: next, effects: state.phase === `background` ? [open, { effect: `sources`, live: true }, { effect: `flush` }] : [open] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { STOPPED2, lifecycleStep2 };
|
|
70
|
+
|
|
71
|
+
//# debugId=4332753BE68640A864756E2164756E21
|
|
72
|
+
//# sourceMappingURL=index-m2845dn6.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../packages/eengine/src/lifecycle.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// VAPP-260: an app instance's lifecycle as one pure reducer. Locked by\n// `fixtures/lifecycle.json`; screens resolve through genUI's host\n// (`resolveScreen` for the app's own taps, `crossAppTarget` for links from outside).\n\nimport { crossAppTarget, resolveScreen } from \"@exponential_app/eui/host\"\n\nexport type LifecyclePhase = `stopped` | `foreground` | `background`\n\nexport interface LifecycleState {\n phase: LifecyclePhase\n screen: string | null\n params: Record<string, string> | null\n}\n\nexport type LifecycleEvent =\n | { type: `launch` }\n | { type: `foreground` }\n | { type: `background` }\n | { type: `terminate` }\n | { type: `online` }\n | { type: `deepLink`; screen?: string | null; params?: Record<string, unknown> | null }\n | { type: `notificationTap`; app: string; screen?: string | null; params?: Record<string, unknown> | null }\n\nexport type LifecycleEffect =\n | { effect: `open`; screen: string; params: Record<string, string> }\n | { effect: `flush` }\n | { effect: `sources`; live: boolean }\n | { effect: `automations`; on: boolean }\n | { effect: `persist`; screen: string; params: Record<string, string> }\n | { effect: `refused`; reason: string }\n\nexport interface LifecyclePackage {\n id: string\n screens?: unknown\n}\n\nexport interface LifecycleContext {\n pkg: LifecyclePackage\n /** The screen the instance persisted last (null = none). */\n lastScreen: { screen: string; params: Record<string, unknown> } | null\n}\n\nexport const STOPPED: LifecycleState = { phase: `stopped`, screen: null, params: null }\n\ntype Target = { screen: string; params: Record<string, string> }\n\n/** The screen a plain launch opens: the persisted one if the package still has it, else the first navigation screen. */\nfunction plainTarget(cx: LifecycleContext): Target | null {\n const last = cx.lastScreen\n const resolved = resolveScreen(cx.pkg, last?.screen ?? null, last?.params ?? null)\n return resolved ? { screen: resolved.screen, params: resolved.params } : null\n}\n\n/** Where a link or tap goes, or why it is refused. */\nfunction linkTarget(event: LifecycleEvent, cx: LifecycleContext): Target | { error: string } | null {\n if (event.type === `deepLink`) return crossAppTarget(cx.pkg, event.screen ?? null, event.params ?? null)\n if (event.type === `notificationTap`) {\n if (event.app !== cx.pkg.id) return { error: `otherApp` }\n const resolved = resolveScreen(cx.pkg, event.screen ?? null, event.params ?? null)\n return resolved ? { screen: resolved.screen, params: resolved.params } : { error: `no such screen` }\n }\n return null\n}\n\nconst ignored = (state: LifecycleState) => ({ state, effects: [] as LifecycleEffect[] })\n\nfunction coldLaunch(target: Target | null, effects: LifecycleEffect[]): { state: LifecycleState; effects: LifecycleEffect[] } {\n effects.push({ effect: `automations`, on: true })\n if (target) effects.push({ effect: `open`, screen: target.screen, params: target.params })\n effects.push({ effect: `sources`, live: true }, { effect: `flush` })\n return { state: { phase: `foreground`, screen: target?.screen ?? null, params: target?.params ?? null }, effects }\n}\n\n/** One lifecycle event → the next state and its effects, in order. */\nexport function lifecycleStep(state: LifecycleState, event: LifecycleEvent, cx: LifecycleContext): { state: LifecycleState; effects: LifecycleEffect[] } {\n const running = state.phase !== `stopped`\n const persist = (): LifecycleEffect[] => (state.screen !== null ? [{ effect: `persist`, screen: state.screen, params: { ...(state.params ?? {}) } }] : [])\n switch (event.type) {\n case `launch`:\n return running ? ignored(state) : coldLaunch(plainTarget(cx), [])\n case `foreground`:\n if (state.phase !== `background`) return ignored(state)\n return { state: { ...state, phase: `foreground` }, effects: [{ effect: `sources`, live: true }, { effect: `flush` }] }\n case `background`:\n if (state.phase !== `foreground`) return ignored(state)\n return { state: { ...state, phase: `background` }, effects: [...persist(), { effect: `sources`, live: false }] }\n case `terminate`:\n if (!running) return ignored(state)\n return {\n state: STOPPED,\n effects: [...persist(), ...(state.phase === `foreground` ? [{ effect: `sources`, live: false } as LifecycleEffect] : []), { effect: `automations`, on: false }],\n }\n case `online`:\n return state.phase === `foreground` ? { state, effects: [{ effect: `flush` }] } : ignored(state)\n case `deepLink`:\n case `notificationTap`: {\n const target = linkTarget(event, cx)\n if (!target) return ignored(state)\n if (`error` in target) {\n const refused: LifecycleEffect = { effect: `refused`, reason: target.error }\n return running ? { state, effects: [refused] } : coldLaunch(plainTarget(cx), [refused])\n }\n if (!running) return coldLaunch(target, [])\n const open: LifecycleEffect = { effect: `open`, screen: target.screen, params: target.params }\n const next: LifecycleState = { phase: `foreground`, screen: target.screen, params: target.params }\n return { state: next, effects: state.phase === `background` ? [open, { effect: `sources`, live: true }, { effect: `flush` }] : [open] }\n }\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAIA;AAsCO,IAAM,WAA0B,EAAE,OAAO,WAAW,QAAQ,MAAM,QAAQ,KAAK;AAKtF,SAAS,WAAW,CAAC,IAAqC;AAAA,EACxD,MAAM,OAAO,GAAG;AAAA,EAChB,MAAM,WAAW,cAAc,GAAG,KAAK,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI;AAAA,EACjF,OAAO,WAAW,EAAE,QAAQ,SAAS,QAAQ,QAAQ,SAAS,OAAO,IAAI;AAAA;AAI3E,SAAS,UAAU,CAAC,OAAuB,IAAyD;AAAA,EAClG,IAAI,MAAM,SAAS;AAAA,IAAY,OAAO,eAAe,GAAG,KAAK,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI;AAAA,EACvG,IAAI,MAAM,SAAS,mBAAmB;AAAA,IACpC,IAAI,MAAM,QAAQ,GAAG,IAAI;AAAA,MAAI,OAAO,EAAE,OAAO,WAAW;AAAA,IACxD,MAAM,WAAW,cAAc,GAAG,KAAK,MAAM,UAAU,MAAM,MAAM,UAAU,IAAI;AAAA,IACjF,OAAO,WAAW,EAAE,QAAQ,SAAS,QAAQ,QAAQ,SAAS,OAAO,IAAI,EAAE,OAAO,iBAAiB;AAAA,EACrG;AAAA,EACA,OAAO;AAAA;AAGT,IAAM,UAAU,CAAC,WAA2B,EAAE,OAAO,SAAS,CAAC,EAAuB;AAEtF,SAAS,UAAU,CAAC,QAAuB,SAAmF;AAAA,EAC5H,QAAQ,KAAK,EAAE,QAAQ,eAAe,IAAI,KAAK,CAAC;AAAA,EAChD,IAAI;AAAA,IAAQ,QAAQ,KAAK,EAAE,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,OAAO,OAAO,CAAC;AAAA,EACzF,QAAQ,KAAK,EAAE,QAAQ,WAAW,MAAM,KAAK,GAAG,EAAE,QAAQ,QAAQ,CAAC;AAAA,EACnE,OAAO,EAAE,OAAO,EAAE,OAAO,cAAc,QAAQ,QAAQ,UAAU,MAAM,QAAQ,QAAQ,UAAU,KAAK,GAAG,QAAQ;AAAA;AAI5G,SAAS,cAAa,CAAC,OAAuB,OAAuB,IAA6E;AAAA,EACvJ,MAAM,UAAU,MAAM,UAAU;AAAA,EAChC,MAAM,UAAU,MAA0B,MAAM,WAAW,OAAO,CAAC,EAAE,QAAQ,WAAW,QAAQ,MAAM,QAAQ,QAAQ,KAAM,MAAM,UAAU,CAAC,EAAG,EAAE,CAAC,IAAI,CAAC;AAAA,EACxJ,QAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,MACH,OAAO,UAAU,QAAQ,KAAK,IAAI,WAAW,YAAY,EAAE,GAAG,CAAC,CAAC;AAAA,IAClE,KAAK;AAAA,MACH,IAAI,MAAM,UAAU;AAAA,QAAc,OAAO,QAAQ,KAAK;AAAA,MACtD,OAAO,EAAE,OAAO,KAAK,OAAO,OAAO,aAAa,GAAG,SAAS,CAAC,EAAE,QAAQ,WAAW,MAAM,KAAK,GAAG,EAAE,QAAQ,QAAQ,CAAC,EAAE;AAAA,IACvH,KAAK;AAAA,MACH,IAAI,MAAM,UAAU;AAAA,QAAc,OAAO,QAAQ,KAAK;AAAA,MACtD,OAAO,EAAE,OAAO,KAAK,OAAO,OAAO,aAAa,GAAG,SAAS,CAAC,GAAG,QAAQ,GAAG,EAAE,QAAQ,WAAW,MAAM,MAAM,CAAC,EAAE;AAAA,IACjH,KAAK;AAAA,MACH,IAAI,CAAC;AAAA,QAAS,OAAO,QAAQ,KAAK;AAAA,MAClC,OAAO;AAAA,QACL,OAAO;AAAA,QACP,SAAS,CAAC,GAAG,QAAQ,GAAG,GAAI,MAAM,UAAU,eAAe,CAAC,EAAE,QAAQ,WAAW,MAAM,MAAM,CAAoB,IAAI,CAAC,GAAI,EAAE,QAAQ,eAAe,IAAI,MAAM,CAAC;AAAA,MAChK;AAAA,IACF,KAAK;AAAA,MACH,OAAO,MAAM,UAAU,eAAe,EAAE,OAAO,SAAS,CAAC,EAAE,QAAQ,QAAQ,CAAC,EAAE,IAAI,QAAQ,KAAK;AAAA,IACjG,KAAK;AAAA,IACL,KAAK,mBAAmB;AAAA,MACtB,MAAM,SAAS,WAAW,OAAO,EAAE;AAAA,MACnC,IAAI,CAAC;AAAA,QAAQ,OAAO,QAAQ,KAAK;AAAA,MACjC,IAAI,WAAW,QAAQ;AAAA,QACrB,MAAM,UAA2B,EAAE,QAAQ,WAAW,QAAQ,OAAO,MAAM;AAAA,QAC3E,OAAO,UAAU,EAAE,OAAO,SAAS,CAAC,OAAO,EAAE,IAAI,WAAW,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC;AAAA,MACxF;AAAA,MACA,IAAI,CAAC;AAAA,QAAS,OAAO,WAAW,QAAQ,CAAC,CAAC;AAAA,MAC1C,MAAM,OAAwB,EAAE,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,OAAO,OAAO;AAAA,MAC7F,MAAM,OAAuB,EAAE,OAAO,cAAc,QAAQ,OAAO,QAAQ,QAAQ,OAAO,OAAO;AAAA,MACjG,OAAO,EAAE,OAAO,MAAM,SAAS,MAAM,UAAU,eAAe,CAAC,MAAM,EAAE,QAAQ,WAAW,MAAM,KAAK,GAAG,EAAE,QAAQ,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;AAAA,IACxI;AAAA;AAAA;",
|
|
8
|
+
"debugId": "4332753BE68640A864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|