@gate-forge/witness 0.0.0-stage → 0.8.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/LICENSE +202 -0
- package/README.md +114 -2
- package/dist/adapter/contract-suite.d.ts +167 -0
- package/dist/adapter/contract-suite.d.ts.map +1 -0
- package/dist/adapter/contract-suite.js +348 -0
- package/dist/adapter/contract-suite.js.map +1 -0
- package/dist/adapter/contract.d.ts +266 -0
- package/dist/adapter/contract.d.ts.map +1 -0
- package/dist/adapter/contract.js +2 -0
- package/dist/adapter/contract.js.map +1 -0
- package/dist/adapter/index.d.ts +8 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +2 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/adapter-kit/config.d.ts +163 -0
- package/dist/adapter-kit/config.d.ts.map +1 -0
- package/dist/adapter-kit/config.js +21 -0
- package/dist/adapter-kit/config.js.map +1 -0
- package/dist/adapter-kit/define.d.ts +47 -0
- package/dist/adapter-kit/define.d.ts.map +1 -0
- package/dist/adapter-kit/define.js +336 -0
- package/dist/adapter-kit/define.js.map +1 -0
- package/dist/adapter-kit/index.d.ts +34 -0
- package/dist/adapter-kit/index.d.ts.map +1 -0
- package/dist/adapter-kit/index.js +32 -0
- package/dist/adapter-kit/index.js.map +1 -0
- package/dist/adapter-kit/projection.d.ts +69 -0
- package/dist/adapter-kit/projection.d.ts.map +1 -0
- package/dist/adapter-kit/projection.js +85 -0
- package/dist/adapter-kit/projection.js.map +1 -0
- package/dist/adapter-kit/session.d.ts +105 -0
- package/dist/adapter-kit/session.d.ts.map +1 -0
- package/dist/adapter-kit/session.js +230 -0
- package/dist/adapter-kit/session.js.map +1 -0
- package/dist/client/witness-client.d.ts +193 -0
- package/dist/client/witness-client.d.ts.map +1 -0
- package/dist/client/witness-client.js +321 -0
- package/dist/client/witness-client.js.map +1 -0
- package/dist/constants.d.ts +189 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +197 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +16 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +21 -0
- package/dist/json.js.map +1 -0
- package/dist/queue/bullmq.d.ts +28 -0
- package/dist/queue/bullmq.d.ts.map +1 -0
- package/dist/queue/bullmq.js +210 -0
- package/dist/queue/bullmq.js.map +1 -0
- package/dist/queue/observer.d.ts +130 -0
- package/dist/queue/observer.d.ts.map +1 -0
- package/dist/queue/observer.js +179 -0
- package/dist/queue/observer.js.map +1 -0
- package/dist/surface.d.ts +211 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +268 -0
- package/dist/surface.js.map +1 -0
- package/dist/witness/adapter-registry.d.ts +92 -0
- package/dist/witness/adapter-registry.d.ts.map +1 -0
- package/dist/witness/adapter-registry.js +370 -0
- package/dist/witness/adapter-registry.js.map +1 -0
- package/dist/witness/behavior-request.d.ts +80 -0
- package/dist/witness/behavior-request.d.ts.map +1 -0
- package/dist/witness/behavior-request.js +335 -0
- package/dist/witness/behavior-request.js.map +1 -0
- package/dist/witness/behavior.d.ts +52 -0
- package/dist/witness/behavior.d.ts.map +1 -0
- package/dist/witness/behavior.js +118 -0
- package/dist/witness/behavior.js.map +1 -0
- package/dist/witness/bin.d.ts +21 -0
- package/dist/witness/bin.d.ts.map +1 -0
- package/dist/witness/bin.js +254 -0
- package/dist/witness/bin.js.map +1 -0
- package/dist/witness/browser.d.ts +221 -0
- package/dist/witness/browser.d.ts.map +1 -0
- package/dist/witness/browser.js +644 -0
- package/dist/witness/browser.js.map +1 -0
- package/dist/witness/chaos.d.ts +179 -0
- package/dist/witness/chaos.d.ts.map +1 -0
- package/dist/witness/chaos.js +270 -0
- package/dist/witness/chaos.js.map +1 -0
- package/dist/witness/classifications.d.ts +42 -0
- package/dist/witness/classifications.d.ts.map +1 -0
- package/dist/witness/classifications.js +87 -0
- package/dist/witness/classifications.js.map +1 -0
- package/dist/witness/env-attestation.d.ts +98 -0
- package/dist/witness/env-attestation.d.ts.map +1 -0
- package/dist/witness/env-attestation.js +202 -0
- package/dist/witness/env-attestation.js.map +1 -0
- package/dist/witness/fixture-provider.d.ts +89 -0
- package/dist/witness/fixture-provider.d.ts.map +1 -0
- package/dist/witness/fixture-provider.js +116 -0
- package/dist/witness/fixture-provider.js.map +1 -0
- package/dist/witness/loopback-pins.d.ts +79 -0
- package/dist/witness/loopback-pins.d.ts.map +1 -0
- package/dist/witness/loopback-pins.js +244 -0
- package/dist/witness/loopback-pins.js.map +1 -0
- package/dist/witness/run-options.d.ts +47 -0
- package/dist/witness/run-options.d.ts.map +1 -0
- package/dist/witness/run-options.js +179 -0
- package/dist/witness/run-options.js.map +1 -0
- package/dist/witness/server.d.ts +34 -0
- package/dist/witness/server.d.ts.map +1 -0
- package/dist/witness/server.js +4899 -0
- package/dist/witness/server.js.map +1 -0
- package/dist/witness/task.d.ts +77 -0
- package/dist/witness/task.d.ts.map +1 -0
- package/dist/witness/task.js +201 -0
- package/dist/witness/task.js.map +1 -0
- package/dist/witness/twin-shapes.d.ts +51 -0
- package/dist/witness/twin-shapes.d.ts.map +1 -0
- package/dist/witness/twin-shapes.js +127 -0
- package/dist/witness/twin-shapes.js.map +1 -0
- package/dist/witness/types.d.ts +1094 -0
- package/dist/witness/types.d.ts.map +1 -0
- package/dist/witness/types.js +2 -0
- package/dist/witness/types.js.map +1 -0
- package/package.json +98 -4
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type PinDnsLookup } from './loopback-pins.js';
|
|
2
|
+
/** One failed attestation, carrying the actionable diagnostic. */
|
|
3
|
+
export declare class AttestationError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Whether a base URL is loopback. Accepts the standard loopback
|
|
8
|
+
* hostnames/IPs (localhost, 127.0.0.0/8, ::1).
|
|
9
|
+
*
|
|
10
|
+
* Args:
|
|
11
|
+
* baseUrl: absolute http(s) URL.
|
|
12
|
+
*
|
|
13
|
+
* Returns:
|
|
14
|
+
* boolean: true when the host is a recognized loopback host.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isLoopbackUrl(baseUrl: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Asserts the attestation subject is loopback (GF-10). Called at witness
|
|
19
|
+
* startup for the target and before EVERY adapter read for the read
|
|
20
|
+
* base. Hostnames that are not literally loopback are resolved through
|
|
21
|
+
* the OS resolver (hosts file + DNS): a name whose addresses are ALL
|
|
22
|
+
* loopback (127.0.0.0/8, ::1) is loopback by construction — the
|
|
23
|
+
* disposable-stack pattern of tenant subdomains mapped to 127.0.0.1.
|
|
24
|
+
* Mixed records, unresolvable names, and lookup failures all reject
|
|
25
|
+
* (fail closed). Approved addresses are PINNED per hostname for the
|
|
26
|
+
* process lifetime (see `loopback-pins.ts`): later DNS changes never
|
|
27
|
+
* move established connections — the check validates
|
|
28
|
+
* OPERATOR-provided bases (never suite input — suites cannot set
|
|
29
|
+
* target/adapter bases), and every egress binds to the startup
|
|
30
|
+
* approval.
|
|
31
|
+
*
|
|
32
|
+
* Args:
|
|
33
|
+
* baseUrl: the base to verify.
|
|
34
|
+
* what: human label for diagnostics ("attestation subject", "adapter 'x'").
|
|
35
|
+
*
|
|
36
|
+
* Throws:
|
|
37
|
+
* AttestationError: when the base is not loopback — the run is BLOCKED
|
|
38
|
+
* before any request is constructed (plan invariant 5).
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertLoopback(baseUrl: string, what: string): Promise<void>;
|
|
41
|
+
/** Signature of the DNS lookup injected for tests (defaults to the OS resolver). */
|
|
42
|
+
export type DnsLookup = PinDnsLookup;
|
|
43
|
+
/** Clears the pin store (tests only — production paths pin once and bind). */
|
|
44
|
+
export declare function clearLoopbackCacheForTests(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Whether a base URL is loopback, resolving non-literal hostnames. The
|
|
47
|
+
* sync string check (`isLoopbackUrl`) runs first; only names it rejects
|
|
48
|
+
* reach the resolver, which pins all-loopback answers (see
|
|
49
|
+
* `loopback-pins.ts` — first resolution wins, later DNS changes never
|
|
50
|
+
* replace the pins).
|
|
51
|
+
*
|
|
52
|
+
* Args:
|
|
53
|
+
* baseUrl: absolute http(s) URL.
|
|
54
|
+
* lookupFn: DNS lookup (default: OS resolver via `node:dns/promises`).
|
|
55
|
+
*
|
|
56
|
+
* Returns:
|
|
57
|
+
* Promise<boolean>: true when literally loopback or ALL resolved
|
|
58
|
+
* addresses are loopback. False on mixed records, unresolvable names,
|
|
59
|
+
* lookup failures, and non-http(s) URLs.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isLoopbackUrlResolving(baseUrl: string, lookupFn?: DnsLookup): Promise<boolean>;
|
|
62
|
+
/** The observed fingerprint + attestation scope of one target probe. */
|
|
63
|
+
export interface EnvProbe {
|
|
64
|
+
/** Fingerprint marker value, or null when the target presents none. */
|
|
65
|
+
fingerprint: string | null;
|
|
66
|
+
/** Attestation-scope marker value, or null. */
|
|
67
|
+
scope: string | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Probes a target's env fingerprint: GET the base and read the marker
|
|
71
|
+
* header. Any transport failure probes as `{fingerprint: null, scope:
|
|
72
|
+
* null}` — absence is a mismatch, never a pass (fail closed).
|
|
73
|
+
*
|
|
74
|
+
* Args:
|
|
75
|
+
* baseUrl: target base to probe.
|
|
76
|
+
* timeoutMs: per-request timeout.
|
|
77
|
+
*
|
|
78
|
+
* Returns:
|
|
79
|
+
* EnvProbe: observed markers (null when absent/unreachable).
|
|
80
|
+
*/
|
|
81
|
+
export declare function probeEnvFingerprint(baseUrl: string, timeoutMs: number): Promise<EnvProbe>;
|
|
82
|
+
/**
|
|
83
|
+
* The same-environment rule for one adapter read (GF-13). The adapter
|
|
84
|
+
* base must present a marker EQUAL to the adapter's declared
|
|
85
|
+
* environmentFingerprint, and (when the run pins one) that fingerprint
|
|
86
|
+
* must equal the run's target fingerprint — the adapter may not read a
|
|
87
|
+
* different environment than the UI under test.
|
|
88
|
+
*
|
|
89
|
+
* Args:
|
|
90
|
+
* probe: observed markers at the adapter base.
|
|
91
|
+
* adapterFingerprint: the adapter's declared environmentFingerprint.
|
|
92
|
+
* targetFingerprint: the run's pinned fingerprint, or null when unset.
|
|
93
|
+
*
|
|
94
|
+
* Returns:
|
|
95
|
+
* string | null: mismatch description, or null when attested.
|
|
96
|
+
*/
|
|
97
|
+
export declare function envFingerprintMismatch(probe: EnvProbe, adapterFingerprint: string, targetFingerprint: string | null): string | null;
|
|
98
|
+
//# sourceMappingURL=env-attestation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-attestation.d.ts","sourceRoot":"","sources":["../../src/witness/env-attestation.ts"],"names":[],"mappings":"AAuBA,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,oBAAoB,CAAC;AAK5B,kEAAkE;AAClE,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAatD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF;AAED,oFAAoF;AACpF,MAAM,MAAM,SAAS,GAAG,YAAY,CAAC;AAErC,8EAA8E;AAC9E,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,SAAyB,GAClC,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ;IACvB,uEAAuE;IACvE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,CAAC,CAsBnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,QAAQ,EACf,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAC/B,MAAM,GAAG,IAAI,CAef"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal v1 environment attestation (GF-10, GF-13; ADR 0002, plan
|
|
3
|
+
* invariant 5, docs/research/environment_attestation.md L0/L1 spirit).
|
|
4
|
+
*
|
|
5
|
+
* Two positive checks, both fail-closed:
|
|
6
|
+
*
|
|
7
|
+
* 1. Loopback-only targets (GF-10): the attestation subject (the SUT the
|
|
8
|
+
* UI drives) and every adapter read base MUST be a loopback address.
|
|
9
|
+
* A non-loopback target blocks the run BEFORE any adapter read is
|
|
10
|
+
* constructed — the witness never builds a request against an
|
|
11
|
+
* unattested base.
|
|
12
|
+
* 2. Env-fingerprint marker (GF-13, minimal v1 attestation): every
|
|
13
|
+
* adapter base must answer GET `/` with an
|
|
14
|
+
* `x-gateforge-env-fingerprint` header equal to the adapter's
|
|
15
|
+
* declared `environmentFingerprint`; when the run also pins
|
|
16
|
+
* `targetFingerprint`, the adapter must read the SAME environment
|
|
17
|
+
* the UI drives (adapter fingerprint === run fingerprint). Absent or
|
|
18
|
+
* mismatched markers reject the record — never `satisfied`.
|
|
19
|
+
*
|
|
20
|
+
* L2 signed statements are deferred per ADR 0002 ("L2 signing deferred").
|
|
21
|
+
*/
|
|
22
|
+
import { lookup } from 'node:dns/promises';
|
|
23
|
+
import { ATTESTATION_SCOPE_HEADER, ENV_FINGERPRINT_HEADER, LOOPBACK_HOSTS } from '../constants.js';
|
|
24
|
+
import { clearPinnedLoopbackForTests, pinLoopbackIps, pinnedGet, } from './loopback-pins.js';
|
|
25
|
+
/** Default DNS lookup: the OS resolver, all records. */
|
|
26
|
+
const defaultLookup = (host) => lookup(host, { all: true, verbatim: true });
|
|
27
|
+
/** One failed attestation, carrying the actionable diagnostic. */
|
|
28
|
+
export class AttestationError extends Error {
|
|
29
|
+
constructor(message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'AttestationError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Whether a base URL is loopback. Accepts the standard loopback
|
|
36
|
+
* hostnames/IPs (localhost, 127.0.0.0/8, ::1).
|
|
37
|
+
*
|
|
38
|
+
* Args:
|
|
39
|
+
* baseUrl: absolute http(s) URL.
|
|
40
|
+
*
|
|
41
|
+
* Returns:
|
|
42
|
+
* boolean: true when the host is a recognized loopback host.
|
|
43
|
+
*/
|
|
44
|
+
export function isLoopbackUrl(baseUrl) {
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = new URL(baseUrl);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
53
|
+
return false;
|
|
54
|
+
const host = parsed.hostname;
|
|
55
|
+
if (LOOPBACK_HOSTS[host])
|
|
56
|
+
return true;
|
|
57
|
+
if (/^127(\.\d{1,3}){3}$/.test(host))
|
|
58
|
+
return true;
|
|
59
|
+
if (host === '0.0.0.0')
|
|
60
|
+
return true;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Asserts the attestation subject is loopback (GF-10). Called at witness
|
|
65
|
+
* startup for the target and before EVERY adapter read for the read
|
|
66
|
+
* base. Hostnames that are not literally loopback are resolved through
|
|
67
|
+
* the OS resolver (hosts file + DNS): a name whose addresses are ALL
|
|
68
|
+
* loopback (127.0.0.0/8, ::1) is loopback by construction — the
|
|
69
|
+
* disposable-stack pattern of tenant subdomains mapped to 127.0.0.1.
|
|
70
|
+
* Mixed records, unresolvable names, and lookup failures all reject
|
|
71
|
+
* (fail closed). Approved addresses are PINNED per hostname for the
|
|
72
|
+
* process lifetime (see `loopback-pins.ts`): later DNS changes never
|
|
73
|
+
* move established connections — the check validates
|
|
74
|
+
* OPERATOR-provided bases (never suite input — suites cannot set
|
|
75
|
+
* target/adapter bases), and every egress binds to the startup
|
|
76
|
+
* approval.
|
|
77
|
+
*
|
|
78
|
+
* Args:
|
|
79
|
+
* baseUrl: the base to verify.
|
|
80
|
+
* what: human label for diagnostics ("attestation subject", "adapter 'x'").
|
|
81
|
+
*
|
|
82
|
+
* Throws:
|
|
83
|
+
* AttestationError: when the base is not loopback — the run is BLOCKED
|
|
84
|
+
* before any request is constructed (plan invariant 5).
|
|
85
|
+
*/
|
|
86
|
+
export async function assertLoopback(baseUrl, what) {
|
|
87
|
+
if (!(await isLoopbackUrlResolving(baseUrl))) {
|
|
88
|
+
throw new AttestationError(`${what} base '${baseUrl}' is not loopback; gateforge only attests disposable ` +
|
|
89
|
+
'loopback environments (plan invariant 5, GF-10). A bare URL with no provider ' +
|
|
90
|
+
'contract is not a mutation target.');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Clears the pin store (tests only — production paths pin once and bind). */
|
|
94
|
+
export function clearLoopbackCacheForTests() {
|
|
95
|
+
clearPinnedLoopbackForTests();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Whether a base URL is loopback, resolving non-literal hostnames. The
|
|
99
|
+
* sync string check (`isLoopbackUrl`) runs first; only names it rejects
|
|
100
|
+
* reach the resolver, which pins all-loopback answers (see
|
|
101
|
+
* `loopback-pins.ts` — first resolution wins, later DNS changes never
|
|
102
|
+
* replace the pins).
|
|
103
|
+
*
|
|
104
|
+
* Args:
|
|
105
|
+
* baseUrl: absolute http(s) URL.
|
|
106
|
+
* lookupFn: DNS lookup (default: OS resolver via `node:dns/promises`).
|
|
107
|
+
*
|
|
108
|
+
* Returns:
|
|
109
|
+
* Promise<boolean>: true when literally loopback or ALL resolved
|
|
110
|
+
* addresses are loopback. False on mixed records, unresolvable names,
|
|
111
|
+
* lookup failures, and non-http(s) URLs.
|
|
112
|
+
*/
|
|
113
|
+
export async function isLoopbackUrlResolving(baseUrl, lookupFn = defaultLookup) {
|
|
114
|
+
if (isLoopbackUrl(baseUrl))
|
|
115
|
+
return true;
|
|
116
|
+
let host;
|
|
117
|
+
try {
|
|
118
|
+
const parsed = new URL(baseUrl);
|
|
119
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
120
|
+
return false;
|
|
121
|
+
host = parsed.hostname;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
await pinLoopbackIps(host, lookupFn);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Probes a target's env fingerprint: GET the base and read the marker
|
|
136
|
+
* header. Any transport failure probes as `{fingerprint: null, scope:
|
|
137
|
+
* null}` — absence is a mismatch, never a pass (fail closed).
|
|
138
|
+
*
|
|
139
|
+
* Args:
|
|
140
|
+
* baseUrl: target base to probe.
|
|
141
|
+
* timeoutMs: per-request timeout.
|
|
142
|
+
*
|
|
143
|
+
* Returns:
|
|
144
|
+
* EnvProbe: observed markers (null when absent/unreachable).
|
|
145
|
+
*/
|
|
146
|
+
export async function probeEnvFingerprint(baseUrl, timeoutMs) {
|
|
147
|
+
try {
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
150
|
+
try {
|
|
151
|
+
// Pinned egress: attested hostnames connect to their
|
|
152
|
+
// startup-approved loopback IPs (Host preserved); unpinned names
|
|
153
|
+
// behave exactly as before.
|
|
154
|
+
const response = await pinnedGet(baseUrl, {
|
|
155
|
+
timeoutMs,
|
|
156
|
+
signal: controller.signal,
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
fingerprint: response.headers.get(ENV_FINGERPRINT_HEADER),
|
|
160
|
+
scope: response.headers.get(ATTESTATION_SCOPE_HEADER),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return { fingerprint: null, scope: null };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The same-environment rule for one adapter read (GF-13). The adapter
|
|
173
|
+
* base must present a marker EQUAL to the adapter's declared
|
|
174
|
+
* environmentFingerprint, and (when the run pins one) that fingerprint
|
|
175
|
+
* must equal the run's target fingerprint — the adapter may not read a
|
|
176
|
+
* different environment than the UI under test.
|
|
177
|
+
*
|
|
178
|
+
* Args:
|
|
179
|
+
* probe: observed markers at the adapter base.
|
|
180
|
+
* adapterFingerprint: the adapter's declared environmentFingerprint.
|
|
181
|
+
* targetFingerprint: the run's pinned fingerprint, or null when unset.
|
|
182
|
+
*
|
|
183
|
+
* Returns:
|
|
184
|
+
* string | null: mismatch description, or null when attested.
|
|
185
|
+
*/
|
|
186
|
+
export function envFingerprintMismatch(probe, adapterFingerprint, targetFingerprint) {
|
|
187
|
+
if (probe.fingerprint === null) {
|
|
188
|
+
return `adapter target presents no '${ENV_FINGERPRINT_HEADER}' marker; ` +
|
|
189
|
+
'the environment is not attested (GF-13)';
|
|
190
|
+
}
|
|
191
|
+
if (probe.fingerprint !== adapterFingerprint) {
|
|
192
|
+
return `adapter target fingerprint '${probe.fingerprint}' does not match the adapter's ` +
|
|
193
|
+
`declared environmentFingerprint '${adapterFingerprint}' (GF-13)`;
|
|
194
|
+
}
|
|
195
|
+
if (targetFingerprint !== null && probe.fingerprint !== targetFingerprint) {
|
|
196
|
+
return `adapter target fingerprint '${probe.fingerprint}' differs from the run's attested ` +
|
|
197
|
+
`target fingerprint '${targetFingerprint}': the adapter would read a different ` +
|
|
198
|
+
'environment than the UI under test (GF-13)';
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=env-attestation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-attestation.js","sourceRoot":"","sources":["../../src/witness/env-attestation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** One leased actor: identity/tenant/roles plus a private credential handle. */
|
|
2
|
+
export interface ActorLease {
|
|
3
|
+
/** Stable principal id within the fixture recipe. */
|
|
4
|
+
principalId: string;
|
|
5
|
+
/** Owning tenant, or null for tenant-less principals. */
|
|
6
|
+
tenantId: string | null;
|
|
7
|
+
/** Role names the actor holds for this lease. */
|
|
8
|
+
roles: string[];
|
|
9
|
+
/**
|
|
10
|
+
* Private engine-side credential reference (opaque handle). NEVER token
|
|
11
|
+
* bytes, NEVER suite-visible.
|
|
12
|
+
*/
|
|
13
|
+
credentialRef: string;
|
|
14
|
+
}
|
|
15
|
+
/** One isolated fixture lease for a single required case. */
|
|
16
|
+
export interface FixtureLease {
|
|
17
|
+
/** Witness-issued lease id (UUID). */
|
|
18
|
+
leaseId: string;
|
|
19
|
+
/** Unique isolated namespace for this case execution. */
|
|
20
|
+
namespace: string;
|
|
21
|
+
/** Authoritative generated subject identities, keyed by recipe name. */
|
|
22
|
+
subjects: Record<string, unknown>;
|
|
23
|
+
/** Leased actors, keyed by recipe profile name. */
|
|
24
|
+
actors: Record<string, ActorLease>;
|
|
25
|
+
}
|
|
26
|
+
/** Input to {@link FixtureProvider.prepare}. */
|
|
27
|
+
export interface FixturePrepareInput {
|
|
28
|
+
/** Approved fixture recipe key (never a suite-supplied callback). */
|
|
29
|
+
recipe: string;
|
|
30
|
+
/** Run id the lease belongs to. */
|
|
31
|
+
runId: string;
|
|
32
|
+
/** Required case id the lease belongs to. */
|
|
33
|
+
caseId: string;
|
|
34
|
+
}
|
|
35
|
+
/** Engine-side credential material (never suite-visible, never logged). */
|
|
36
|
+
export interface CredentialMaterial {
|
|
37
|
+
/** Headers the engine driver attaches to the principal request. */
|
|
38
|
+
headers: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
/** Trusted fixture/actor provider interface. */
|
|
41
|
+
export interface FixtureProvider {
|
|
42
|
+
/** Mints a fresh isolated lease for one required case. */
|
|
43
|
+
prepare(input: FixturePrepareInput): Promise<FixtureLease> | FixtureLease;
|
|
44
|
+
/** Releases a lease namespace (idempotent; errors never flip verdicts). */
|
|
45
|
+
release(leaseId: string): Promise<void> | void;
|
|
46
|
+
/**
|
|
47
|
+
* Resolves a lease credentialRef to request material, engine-side
|
|
48
|
+
* only. Returns null for unknown/expired references. The bytes never
|
|
49
|
+
* leave the controller/engine boundary.
|
|
50
|
+
*/
|
|
51
|
+
resolveCredential?(credentialRef: string): Promise<CredentialMaterial | null> | CredentialMaterial | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Loads the approved fixture provider from the engine-owned bundle
|
|
55
|
+
* (a `fixture-provider.mjs` default export implementing the
|
|
56
|
+
* interface). `bundleDir` may be the bundle DIRECTORY or the provider
|
|
57
|
+
* MODULE itself, so the operator variable can name either. Returns null
|
|
58
|
+
* when no provider exists — strong cases then block with a
|
|
59
|
+
* missing-provider cause instead of falling back to suite-supplied
|
|
60
|
+
* fixtures.
|
|
61
|
+
*
|
|
62
|
+
* Args:
|
|
63
|
+
* bundleDir: absolute engine-owned bundle directory or provider
|
|
64
|
+
* module path, or null.
|
|
65
|
+
*
|
|
66
|
+
* Returns:
|
|
67
|
+
* Promise<FixtureProvider | null>: the approved provider or null.
|
|
68
|
+
*
|
|
69
|
+
* Throws:
|
|
70
|
+
* Error: when the module exists but does not implement the interface
|
|
71
|
+
* (fail closed — a half-shaped provider is never used).
|
|
72
|
+
*/
|
|
73
|
+
export declare function loadFixtureProvider(bundleDir: string | null): Promise<FixtureProvider | null>;
|
|
74
|
+
/** In-memory provider for witness tests (engine-side only, never a worker API). */
|
|
75
|
+
export declare function createMemoryFixtureProvider(options?: {
|
|
76
|
+
/** Actor profiles available to recipes: profile name → lease template. */
|
|
77
|
+
actors?: Record<string, {
|
|
78
|
+
principalId: string;
|
|
79
|
+
tenantId: string | null;
|
|
80
|
+
roles: string[];
|
|
81
|
+
}>;
|
|
82
|
+
/** Engine-side credential headers per actor profile (test secrets, never suite-visible). */
|
|
83
|
+
credentials?: Record<string, Record<string, string>>;
|
|
84
|
+
/** Fixture subjects per recipe: recipe name → subject key → value. */
|
|
85
|
+
subjects?: Record<string, Record<string, unknown>>;
|
|
86
|
+
}): FixtureProvider & {
|
|
87
|
+
leases(): FixtureLease[];
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=fixture-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixture-provider.d.ts","sourceRoot":"","sources":["../../src/witness/fixture-provider.ts"],"names":[],"mappings":"AAqBA,gFAAgF;AAChF,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iDAAiD;IACjD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,OAAO,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;IAC1E,2EAA2E;IAC3E,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/C;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,kBAAkB,GAAG,IAAI,CAAC;CAC3G;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAqBnG;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CAAC,OAAO,GAAE;IACnD,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC3F,4FAA4F;IAC5F,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC/C,GAAG,eAAe,GAAG;IAAE,MAAM,IAAI,YAAY,EAAE,CAAA;CAAE,CAmDtD"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trusted fixture/actor provider (plan 2026-09-19 §4.5): the ONLY source
|
|
3
|
+
* of fixture namespaces, authoritative subject identities, and actor
|
|
4
|
+
* credentials for strong behavior cases. Loaded ONLY from the approved
|
|
5
|
+
* engine-owned bundle — never from suite-supplied callbacks, never from
|
|
6
|
+
* `AdapterContext.get` or `probeServer`.
|
|
7
|
+
*
|
|
8
|
+
* - `prepare` mints a fresh isolated lease per case (unique namespace);
|
|
9
|
+
* sequence steps share only their own case lease.
|
|
10
|
+
* - Actor material resolves privately inside the controller/engine
|
|
11
|
+
* request driver. Cookie/header bytes never appear in suite-visible
|
|
12
|
+
* results, signed public reports, argv, or logs — the lease carries a
|
|
13
|
+
* `credentialRef` (an engine-side handle), never token bytes.
|
|
14
|
+
* - `release` cleans the lease namespace; cleanup runs on errors without
|
|
15
|
+
* flipping the case verdict to success.
|
|
16
|
+
*/
|
|
17
|
+
import { randomUUID } from 'node:crypto';
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { pathToFileURL } from 'node:url';
|
|
21
|
+
/**
|
|
22
|
+
* Loads the approved fixture provider from the engine-owned bundle
|
|
23
|
+
* (a `fixture-provider.mjs` default export implementing the
|
|
24
|
+
* interface). `bundleDir` may be the bundle DIRECTORY or the provider
|
|
25
|
+
* MODULE itself, so the operator variable can name either. Returns null
|
|
26
|
+
* when no provider exists — strong cases then block with a
|
|
27
|
+
* missing-provider cause instead of falling back to suite-supplied
|
|
28
|
+
* fixtures.
|
|
29
|
+
*
|
|
30
|
+
* Args:
|
|
31
|
+
* bundleDir: absolute engine-owned bundle directory or provider
|
|
32
|
+
* module path, or null.
|
|
33
|
+
*
|
|
34
|
+
* Returns:
|
|
35
|
+
* Promise<FixtureProvider | null>: the approved provider or null.
|
|
36
|
+
*
|
|
37
|
+
* Throws:
|
|
38
|
+
* Error: when the module exists but does not implement the interface
|
|
39
|
+
* (fail closed — a half-shaped provider is never used).
|
|
40
|
+
*/
|
|
41
|
+
export async function loadFixtureProvider(bundleDir) {
|
|
42
|
+
if (bundleDir === null)
|
|
43
|
+
return null;
|
|
44
|
+
// Runtime-selected module specifier: the provider path is operator
|
|
45
|
+
// input resolved at startup, never known at author time.
|
|
46
|
+
const entry = bundleDir.endsWith('.mjs') || bundleDir.endsWith('.js')
|
|
47
|
+
? bundleDir
|
|
48
|
+
: join(bundleDir, 'fixture-provider.mjs');
|
|
49
|
+
if (!existsSync(entry))
|
|
50
|
+
return null;
|
|
51
|
+
const module = (await import(pathToFileURL(entry).href));
|
|
52
|
+
const provider = module.default;
|
|
53
|
+
if (provider === undefined ||
|
|
54
|
+
typeof provider.prepare !== 'function' ||
|
|
55
|
+
typeof provider.release !== 'function') {
|
|
56
|
+
throw new Error('approved fixture provider is malformed: default export must implement {prepare, release} (fail closed)');
|
|
57
|
+
}
|
|
58
|
+
return provider;
|
|
59
|
+
}
|
|
60
|
+
/** In-memory provider for witness tests (engine-side only, never a worker API). */
|
|
61
|
+
export function createMemoryFixtureProvider(options = {}) {
|
|
62
|
+
const live = new Map();
|
|
63
|
+
let counter = 0;
|
|
64
|
+
const actors = options.actors ?? {};
|
|
65
|
+
const credentials = options.credentials ?? {};
|
|
66
|
+
const subjects = options.subjects ?? {};
|
|
67
|
+
const credentialFor = (credentialRef) => {
|
|
68
|
+
const match = /^credref:([^:]+):(.+)$/.exec(credentialRef);
|
|
69
|
+
if (match === null)
|
|
70
|
+
return null;
|
|
71
|
+
const profile = match[2];
|
|
72
|
+
if (!live.has(match[1]))
|
|
73
|
+
return null;
|
|
74
|
+
const headers = credentials[profile];
|
|
75
|
+
if (headers === undefined)
|
|
76
|
+
return null;
|
|
77
|
+
return { headers: { ...headers } };
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
prepare(input) {
|
|
81
|
+
counter += 1;
|
|
82
|
+
const leaseId = randomUUID();
|
|
83
|
+
const namespace = `fixture-${input.runId}-${input.caseId}-${String(counter)}`
|
|
84
|
+
.toLowerCase()
|
|
85
|
+
.replace(/[^a-z0-9-]/g, '-');
|
|
86
|
+
const leasedActors = {};
|
|
87
|
+
for (const [name, template] of Object.entries(actors)) {
|
|
88
|
+
leasedActors[name] = {
|
|
89
|
+
principalId: template.principalId,
|
|
90
|
+
tenantId: template.tenantId,
|
|
91
|
+
roles: [...template.roles],
|
|
92
|
+
credentialRef: `credref:${leaseId}:${name}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const recipeSubjects = subjects[input.recipe];
|
|
96
|
+
const lease = {
|
|
97
|
+
leaseId,
|
|
98
|
+
namespace,
|
|
99
|
+
subjects: recipeSubjects === undefined ? {} : JSON.parse(JSON.stringify(recipeSubjects)),
|
|
100
|
+
actors: leasedActors,
|
|
101
|
+
};
|
|
102
|
+
live.set(leaseId, lease);
|
|
103
|
+
return lease;
|
|
104
|
+
},
|
|
105
|
+
release(leaseId) {
|
|
106
|
+
live.delete(leaseId);
|
|
107
|
+
},
|
|
108
|
+
resolveCredential(credentialRef) {
|
|
109
|
+
return credentialFor(credentialRef);
|
|
110
|
+
},
|
|
111
|
+
leases() {
|
|
112
|
+
return [...live.values()];
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=fixture-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixture-provider.js","sourceRoot":"","sources":["../../src/witness/fixture-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** DNS lookup signature (injectable for tests; default: OS resolver). */
|
|
2
|
+
export type PinDnsLookup = (host: string) => Promise<ReadonlyArray<{
|
|
3
|
+
address: string;
|
|
4
|
+
family: number;
|
|
5
|
+
}>>;
|
|
6
|
+
/** Whether one address is loopback (127.0.0.0/8 or ::1). */
|
|
7
|
+
export declare function isLoopbackAddress(address: string): boolean;
|
|
8
|
+
/** Clears all pins (tests only — production paths pin once and bind). */
|
|
9
|
+
export declare function clearPinnedLoopbackForTests(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Resolves a hostname and pins ALL-loopback addresses, or rejects.
|
|
12
|
+
* The first successful pinning wins for the process lifetime: later
|
|
13
|
+
* resolutions (including hostile mid-run DNS changes) never replace
|
|
14
|
+
* it — connections bind to the startup-approved set.
|
|
15
|
+
*
|
|
16
|
+
* Args:
|
|
17
|
+
* hostname: the operator-provided hostname to attest.
|
|
18
|
+
* lookupFn: DNS lookup (default: OS resolver, all records).
|
|
19
|
+
*
|
|
20
|
+
* Returns:
|
|
21
|
+
* The pinned loopback IP list (IPv4 first, then IPv6, stable order).
|
|
22
|
+
*
|
|
23
|
+
* Throws:
|
|
24
|
+
* Error: on empty/mixed/public/unresolvable answers.
|
|
25
|
+
*/
|
|
26
|
+
export declare function pinLoopbackIps(hostname: string, lookupFn?: PinDnsLookup): Promise<string[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Snapshot copy of current pins (hostname → approved IPs).
|
|
29
|
+
*
|
|
30
|
+
* Returns:
|
|
31
|
+
* A detached copy for launch-arg construction.
|
|
32
|
+
*/
|
|
33
|
+
export declare function pinnedLoopbackIps(): Map<string, string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Builds the Chromium `--host-resolver-rules` value binding every
|
|
36
|
+
* pinned hostname to its approved IPs (`MAP host ip`, comma-separated,
|
|
37
|
+
* deterministic order). IP literals are emitted bare (brackets are URL
|
|
38
|
+
* syntax and would break rule parsing).
|
|
39
|
+
*
|
|
40
|
+
* Args:
|
|
41
|
+
* pins: hostname → approved IPs (default: live snapshot).
|
|
42
|
+
*
|
|
43
|
+
* Returns:
|
|
44
|
+
* The rules string, or null when nothing is pinned (no flag then).
|
|
45
|
+
*/
|
|
46
|
+
export declare function hostResolverRules(pins?: ReadonlyMap<string, readonly string[]>): string | null;
|
|
47
|
+
/** Pinned GET result (fetch-compatible subset used by adapter/probe reads). */
|
|
48
|
+
export interface PinnedGetResult {
|
|
49
|
+
status: number;
|
|
50
|
+
headers: Headers;
|
|
51
|
+
json(): Promise<unknown>;
|
|
52
|
+
text(): Promise<string>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* GET through the startup pins: when the URL's hostname is pinned, the
|
|
56
|
+
* TCP connection goes to the pinned IP while the original Host header
|
|
57
|
+
* (host + non-default port) and path are preserved — tenant routing
|
|
58
|
+
* intact, destination bound. Unpinned hostnames use plain global fetch
|
|
59
|
+
* (previous behavior, unchanged).
|
|
60
|
+
*
|
|
61
|
+
* Args:
|
|
62
|
+
* url: absolute http(s) URL.
|
|
63
|
+
* options: timeout, headers, abort signal, redirect budget.
|
|
64
|
+
*
|
|
65
|
+
* Returns:
|
|
66
|
+
* Status + headers + body readers.
|
|
67
|
+
*
|
|
68
|
+
* Throws:
|
|
69
|
+
* Error: on transport failure, redirect overflow, or (https) the
|
|
70
|
+
* pinned IP failing certificate verification — fail closed, never
|
|
71
|
+
* plaintext fallback.
|
|
72
|
+
*/
|
|
73
|
+
export declare function pinnedGet(url: string, options: {
|
|
74
|
+
timeoutMs: number;
|
|
75
|
+
headers?: Record<string, string>;
|
|
76
|
+
signal?: AbortSignal;
|
|
77
|
+
maxRedirects?: number;
|
|
78
|
+
}): Promise<PinnedGetResult>;
|
|
79
|
+
//# sourceMappingURL=loopback-pins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback-pins.d.ts","sourceRoot":"","sources":["../../src/witness/loopback-pins.ts"],"names":[],"mappings":"AA4BA,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG,CACzB,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,aAAa,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAAC;AAEjE,4DAA4D;AAC5D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1D;AAKD,yEAAyE;AACzE,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,YAAoE,GAC7E,OAAO,CAAC,MAAM,EAAE,CAAC,CAiBnB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAEzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAuB,GAAG,MAAM,GAAG,IAAI,CAQnH;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IACP,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACA,OAAO,CAAC,eAAe,CAAC,CAgC1B"}
|