instar 1.3.587 → 1.3.589
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +237 -9
- package/dist/commands/server.js.map +1 -1
- package/dist/core/LiveTestArtifactStore.d.ts +129 -0
- package/dist/core/LiveTestArtifactStore.d.ts.map +1 -0
- package/dist/core/LiveTestArtifactStore.js +184 -0
- package/dist/core/LiveTestArtifactStore.js.map +1 -0
- package/dist/core/LiveTestGate.d.ts +54 -0
- package/dist/core/LiveTestGate.d.ts.map +1 -0
- package/dist/core/LiveTestGate.js +124 -0
- package/dist/core/LiveTestGate.js.map +1 -0
- package/dist/core/LiveTestHarness.d.ts +99 -0
- package/dist/core/LiveTestHarness.d.ts.map +1 -0
- package/dist/core/LiveTestHarness.js +102 -0
- package/dist/core/LiveTestHarness.js.map +1 -0
- package/dist/core/LocalSessionOwnershipStore.d.ts +56 -0
- package/dist/core/LocalSessionOwnershipStore.d.ts.map +1 -0
- package/dist/core/LocalSessionOwnershipStore.js +130 -0
- package/dist/core/LocalSessionOwnershipStore.js.map +1 -0
- package/dist/core/OwnershipApplier.d.ts +78 -0
- package/dist/core/OwnershipApplier.d.ts.map +1 -0
- package/dist/core/OwnershipApplier.js +101 -0
- package/dist/core/OwnershipApplier.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +9 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/WorkEvidence.d.ts +17 -0
- package/dist/core/WorkEvidence.d.ts.map +1 -1
- package/dist/core/WorkEvidence.js +17 -0
- package/dist/core/WorkEvidence.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +12 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/gapBCommitmentEvidence.d.ts +143 -0
- package/dist/core/gapBCommitmentEvidence.d.ts.map +1 -0
- package/dist/core/gapBCommitmentEvidence.js +138 -0
- package/dist/core/gapBCommitmentEvidence.js.map +1 -0
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/CommitmentTracker.d.ts +12 -0
- package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
- package/dist/monitoring/CommitmentTracker.js +14 -0
- package/dist/monitoring/CommitmentTracker.js.map +1 -1
- package/dist/monitoring/ReapLog.d.ts +11 -0
- package/dist/monitoring/ReapLog.d.ts.map +1 -1
- package/dist/monitoring/ReapLog.js +1 -0
- package/dist/monitoring/ReapLog.js.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.d.ts +15 -0
- package/dist/monitoring/ResumeQueueDrainer.d.ts.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.js +12 -1
- package/dist/monitoring/ResumeQueueDrainer.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +2 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts +4 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +58 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +2 -0
- package/upgrades/1.3.588.md +27 -0
- package/upgrades/1.3.589.md +68 -0
- package/upgrades/side-effects/autonomous-registration-guarantee.md +51 -0
- package/upgrades/side-effects/live-user-channel-proof-standard.md +54 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveTestArtifactStore — the durable, signed, hash-chained record a user-role
|
|
3
|
+
* live-test run produces, and the object the completion gate reads (spec
|
|
4
|
+
* docs/specs/live-user-channel-proof-standard.md §4.4). It is the shared contract
|
|
5
|
+
* between the harness (which WRITES it) and the gate (which VERIFIES it).
|
|
6
|
+
*
|
|
7
|
+
* Anti-hallucination (the load-bearing property): the artifact is machine-written
|
|
8
|
+
* and SIGNED, so an agent cannot hand-type a "I tested it" record into its
|
|
9
|
+
* transcript to buy the "done" exit. Concretely:
|
|
10
|
+
* - the artifact is canonically serialized (sorted keys) and content-hashed;
|
|
11
|
+
* - the hash is Ed25519-signed by the harness runner identity;
|
|
12
|
+
* - each machine appends ONLY to its OWN ledger segment
|
|
13
|
+
* (state/live-test-ledger.<machineId>.jsonl), hash-chained WITHIN the segment,
|
|
14
|
+
* so cross-machine replication can never fork a shared chain or raise a false
|
|
15
|
+
* broken-chain veto (§4.4, codex r4);
|
|
16
|
+
* - the gate re-reads the artifact from disk, recomputes the hash, and checks it
|
|
17
|
+
* against the ledger entry — the transcript is NEVER the evidence.
|
|
18
|
+
*
|
|
19
|
+
* Threat model (honest, like UnjustifiedStopGate): drift-correction, NOT a security
|
|
20
|
+
* boundary against a deliberately compromised runner. The git-commit anchor (the
|
|
21
|
+
* artifact + ledger are committed as part of the run) is the out-of-band check.
|
|
22
|
+
*/
|
|
23
|
+
export type RiskCategory = 'happy-path' | 'channel-parity' | 'lifecycle' | 'permission-volatile' | 'failure-rollback' | 'concurrency' | 'idempotency' | 'regression';
|
|
24
|
+
export declare const REQUIRED_RISK_CATEGORIES: RiskCategory[];
|
|
25
|
+
/** A channel feature requires {telegram, slack}; a dashboard feature, dashboard. */
|
|
26
|
+
export type Surface = 'telegram' | 'slack' | 'dashboard';
|
|
27
|
+
export type ScenarioVerdict = 'PASS' | 'FAIL' | 'BLOCKED';
|
|
28
|
+
export type BlockedKind = 'platform-error' | 'platform-outage' | 'operator-waiver' | 'timeout' | 'credential-unavailable';
|
|
29
|
+
/** Deterministic protocol evidence captured from the real platform (§4.4). */
|
|
30
|
+
export interface ScenarioEvidence {
|
|
31
|
+
messageIds?: string[];
|
|
32
|
+
channelId?: string;
|
|
33
|
+
senderId?: string;
|
|
34
|
+
responderMachineId?: string;
|
|
35
|
+
ownershipSnapshot?: Record<string, unknown>;
|
|
36
|
+
[k: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
export interface ScenarioRow {
|
|
39
|
+
id: string;
|
|
40
|
+
description: string;
|
|
41
|
+
surface: Surface;
|
|
42
|
+
riskCategory: RiskCategory;
|
|
43
|
+
verdict: ScenarioVerdict;
|
|
44
|
+
evidence?: ScenarioEvidence;
|
|
45
|
+
/** Required when verdict === 'BLOCKED' — only a machine-verifiable external
|
|
46
|
+
* blocker is honored (§4.6); a bare BLOCKED counts as FAIL at the gate. */
|
|
47
|
+
blockedKind?: BlockedKind;
|
|
48
|
+
blockedReason?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface LiveTestArtifact {
|
|
51
|
+
featureId: string;
|
|
52
|
+
runId: string;
|
|
53
|
+
surfaces: Surface[];
|
|
54
|
+
riskCategories: RiskCategory[];
|
|
55
|
+
scenarios: ScenarioRow[];
|
|
56
|
+
createdAt: string;
|
|
57
|
+
runnerFingerprint: string;
|
|
58
|
+
}
|
|
59
|
+
export interface LedgerEntry {
|
|
60
|
+
featureId: string;
|
|
61
|
+
runId: string;
|
|
62
|
+
contentHash: string;
|
|
63
|
+
signature: string;
|
|
64
|
+
signerFingerprint: string;
|
|
65
|
+
surfaces: Surface[];
|
|
66
|
+
riskCategories: RiskCategory[];
|
|
67
|
+
createdAt: string;
|
|
68
|
+
/** Hash of the PRIOR entry in THIS machine's segment (null for the first). */
|
|
69
|
+
prevEntryHash: string | null;
|
|
70
|
+
}
|
|
71
|
+
/** Stable, sorted-key JSON serialization so the content hash is deterministic. */
|
|
72
|
+
export declare function canonicalize(value: unknown): string;
|
|
73
|
+
export interface LiveTestArtifactStoreDeps {
|
|
74
|
+
stateDir: string;
|
|
75
|
+
/** This machine's id — names its ledger segment (no cross-machine concurrent append). */
|
|
76
|
+
machineId: string;
|
|
77
|
+
/** The harness runner identity recorded in the ledger entry. */
|
|
78
|
+
signerFingerprint: string;
|
|
79
|
+
/** Ed25519 sign over a string → base64 signature (e.g. MachineIdentity.sign bound to the key). */
|
|
80
|
+
sign: (data: string) => string;
|
|
81
|
+
/** Verify a base64 signature over a string against the runner's trusted key. */
|
|
82
|
+
verify: (data: string, sig: string) => boolean;
|
|
83
|
+
now?: () => number;
|
|
84
|
+
logger?: (m: string) => void;
|
|
85
|
+
}
|
|
86
|
+
export interface VerifyResult {
|
|
87
|
+
ok: boolean;
|
|
88
|
+
reason?: 'no-entry' | 'artifact-missing' | 'hash-mismatch' | 'bad-signature';
|
|
89
|
+
entry?: LedgerEntry;
|
|
90
|
+
artifact?: LiveTestArtifact;
|
|
91
|
+
}
|
|
92
|
+
export declare class LiveTestArtifactStore {
|
|
93
|
+
private readonly d;
|
|
94
|
+
constructor(deps: LiveTestArtifactStoreDeps);
|
|
95
|
+
private now;
|
|
96
|
+
private log;
|
|
97
|
+
private artifactsDir;
|
|
98
|
+
private artifactPath;
|
|
99
|
+
private ownSegmentPath;
|
|
100
|
+
private writeFileAtomic;
|
|
101
|
+
/** Read this machine's ledger segment (own appends only). */
|
|
102
|
+
private readOwnSegment;
|
|
103
|
+
private readSegmentFile;
|
|
104
|
+
/**
|
|
105
|
+
* Write a signed artifact + append its hash-chained ledger entry to THIS
|
|
106
|
+
* machine's segment. Returns the ledger entry. The content hash is computed at
|
|
107
|
+
* write-time over the canonical artifact; the signature is over that hash.
|
|
108
|
+
*/
|
|
109
|
+
write(artifact: LiveTestArtifact): LedgerEntry;
|
|
110
|
+
/** Read the on-disk artifact for an entry (or null). */
|
|
111
|
+
readArtifact(featureId: string, runId: string): LiveTestArtifact | null;
|
|
112
|
+
/** Every ledger entry across ALL machine segments (the derived union, §10). */
|
|
113
|
+
allEntries(): LedgerEntry[];
|
|
114
|
+
/**
|
|
115
|
+
* Verify a specific {featureId, runId}: the entry exists, the artifact is on
|
|
116
|
+
* disk, its recomputed content hash matches the entry (tamper check), and the
|
|
117
|
+
* signature verifies. This is what the gate calls — the transcript is never the
|
|
118
|
+
* evidence.
|
|
119
|
+
*/
|
|
120
|
+
verifyEntry(featureId: string, runId: string): VerifyResult;
|
|
121
|
+
/** Verify THIS machine's segment hash-chain (a break = tamper signal, §4.4). */
|
|
122
|
+
verifyOwnChain(): {
|
|
123
|
+
ok: boolean;
|
|
124
|
+
brokenAtIndex?: number;
|
|
125
|
+
};
|
|
126
|
+
/** The freshest VERIFIED entry for a feature across all segments (or null). */
|
|
127
|
+
latestVerified(featureId: string): VerifyResult | null;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=LiveTestArtifactStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveTestArtifactStore.d.ts","sourceRoot":"","sources":["../../src/core/LiveTestArtifactStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveTestArtifactStore — the durable, signed, hash-chained record a user-role
|
|
3
|
+
* live-test run produces, and the object the completion gate reads (spec
|
|
4
|
+
* docs/specs/live-user-channel-proof-standard.md §4.4). It is the shared contract
|
|
5
|
+
* between the harness (which WRITES it) and the gate (which VERIFIES it).
|
|
6
|
+
*
|
|
7
|
+
* Anti-hallucination (the load-bearing property): the artifact is machine-written
|
|
8
|
+
* and SIGNED, so an agent cannot hand-type a "I tested it" record into its
|
|
9
|
+
* transcript to buy the "done" exit. Concretely:
|
|
10
|
+
* - the artifact is canonically serialized (sorted keys) and content-hashed;
|
|
11
|
+
* - the hash is Ed25519-signed by the harness runner identity;
|
|
12
|
+
* - each machine appends ONLY to its OWN ledger segment
|
|
13
|
+
* (state/live-test-ledger.<machineId>.jsonl), hash-chained WITHIN the segment,
|
|
14
|
+
* so cross-machine replication can never fork a shared chain or raise a false
|
|
15
|
+
* broken-chain veto (§4.4, codex r4);
|
|
16
|
+
* - the gate re-reads the artifact from disk, recomputes the hash, and checks it
|
|
17
|
+
* against the ledger entry — the transcript is NEVER the evidence.
|
|
18
|
+
*
|
|
19
|
+
* Threat model (honest, like UnjustifiedStopGate): drift-correction, NOT a security
|
|
20
|
+
* boundary against a deliberately compromised runner. The git-commit anchor (the
|
|
21
|
+
* artifact + ledger are committed as part of the run) is the out-of-band check.
|
|
22
|
+
*/
|
|
23
|
+
import crypto from 'node:crypto';
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
export const REQUIRED_RISK_CATEGORIES = [
|
|
27
|
+
'happy-path', 'channel-parity', 'lifecycle', 'permission-volatile',
|
|
28
|
+
'failure-rollback', 'concurrency', 'idempotency', 'regression',
|
|
29
|
+
];
|
|
30
|
+
/** Stable, sorted-key JSON serialization so the content hash is deterministic. */
|
|
31
|
+
export function canonicalize(value) {
|
|
32
|
+
const seen = new WeakSet();
|
|
33
|
+
const norm = (v) => {
|
|
34
|
+
if (v === null || typeof v !== 'object')
|
|
35
|
+
return v;
|
|
36
|
+
if (seen.has(v))
|
|
37
|
+
throw new Error('canonicalize: cyclic value');
|
|
38
|
+
seen.add(v);
|
|
39
|
+
if (Array.isArray(v))
|
|
40
|
+
return v.map(norm);
|
|
41
|
+
const o = v;
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const k of Object.keys(o).sort())
|
|
44
|
+
out[k] = norm(o[k]);
|
|
45
|
+
return out;
|
|
46
|
+
};
|
|
47
|
+
return JSON.stringify(norm(value));
|
|
48
|
+
}
|
|
49
|
+
function sha256(s) {
|
|
50
|
+
return crypto.createHash('sha256').update(s).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
export class LiveTestArtifactStore {
|
|
53
|
+
d;
|
|
54
|
+
constructor(deps) {
|
|
55
|
+
this.d = deps;
|
|
56
|
+
}
|
|
57
|
+
now() { return (this.d.now ?? Date.now)(); }
|
|
58
|
+
log(m) { this.d.logger?.(`[live-test-artifact] ${m}`); }
|
|
59
|
+
artifactsDir() { return path.join(this.d.stateDir, 'live-test-artifacts'); }
|
|
60
|
+
artifactPath(featureId, runId) {
|
|
61
|
+
const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
62
|
+
return path.join(this.artifactsDir(), safe(featureId), `${safe(runId)}.json`);
|
|
63
|
+
}
|
|
64
|
+
ownSegmentPath() {
|
|
65
|
+
const safe = this.d.machineId.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
66
|
+
return path.join(this.d.stateDir, `live-test-ledger.${safe}.jsonl`);
|
|
67
|
+
}
|
|
68
|
+
writeFileAtomic(fp, data) {
|
|
69
|
+
const dir = path.dirname(fp);
|
|
70
|
+
if (!fs.existsSync(dir))
|
|
71
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
72
|
+
const tmp = `${fp}.${process.pid}.tmp`;
|
|
73
|
+
fs.writeFileSync(tmp, data);
|
|
74
|
+
fs.renameSync(tmp, fp);
|
|
75
|
+
}
|
|
76
|
+
/** Read this machine's ledger segment (own appends only). */
|
|
77
|
+
readOwnSegment() {
|
|
78
|
+
return this.readSegmentFile(this.ownSegmentPath());
|
|
79
|
+
}
|
|
80
|
+
readSegmentFile(fp) {
|
|
81
|
+
try {
|
|
82
|
+
if (!fs.existsSync(fp))
|
|
83
|
+
return [];
|
|
84
|
+
return fs.readFileSync(fp, 'utf-8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return []; // @silent-fallback-ok — an unreadable/corrupt segment yields no entries (not-proven), never a throw
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Write a signed artifact + append its hash-chained ledger entry to THIS
|
|
92
|
+
* machine's segment. Returns the ledger entry. The content hash is computed at
|
|
93
|
+
* write-time over the canonical artifact; the signature is over that hash.
|
|
94
|
+
*/
|
|
95
|
+
write(artifact) {
|
|
96
|
+
const canonical = canonicalize(artifact);
|
|
97
|
+
const contentHash = sha256(canonical);
|
|
98
|
+
const signature = this.d.sign(contentHash);
|
|
99
|
+
this.writeFileAtomic(this.artifactPath(artifact.featureId, artifact.runId), canonical);
|
|
100
|
+
const own = this.readOwnSegment();
|
|
101
|
+
const prevEntryHash = own.length ? sha256(canonicalize(own[own.length - 1])) : null;
|
|
102
|
+
const entry = {
|
|
103
|
+
featureId: artifact.featureId,
|
|
104
|
+
runId: artifact.runId,
|
|
105
|
+
contentHash,
|
|
106
|
+
signature,
|
|
107
|
+
signerFingerprint: this.d.signerFingerprint,
|
|
108
|
+
surfaces: artifact.surfaces,
|
|
109
|
+
riskCategories: artifact.riskCategories,
|
|
110
|
+
createdAt: artifact.createdAt,
|
|
111
|
+
prevEntryHash,
|
|
112
|
+
};
|
|
113
|
+
fs.appendFileSync(this.ownSegmentPath(), `${JSON.stringify(entry)}\n`);
|
|
114
|
+
this.log(`wrote artifact ${artifact.featureId}/${artifact.runId} (hash ${contentHash.slice(0, 12)}…, ${artifact.scenarios.length} scenarios)`);
|
|
115
|
+
return entry;
|
|
116
|
+
}
|
|
117
|
+
/** Read the on-disk artifact for an entry (or null). */
|
|
118
|
+
readArtifact(featureId, runId) {
|
|
119
|
+
try {
|
|
120
|
+
const fp = this.artifactPath(featureId, runId);
|
|
121
|
+
if (!fs.existsSync(fp))
|
|
122
|
+
return null;
|
|
123
|
+
return JSON.parse(fs.readFileSync(fp, 'utf-8'));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null; // @silent-fallback-ok — missing/corrupt artifact → null (gate treats as not-proven)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Every ledger entry across ALL machine segments (the derived union, §10). */
|
|
130
|
+
allEntries() {
|
|
131
|
+
const out = [];
|
|
132
|
+
try {
|
|
133
|
+
for (const f of fs.readdirSync(this.d.stateDir)) {
|
|
134
|
+
if (/^live-test-ledger\..+\.jsonl$/.test(f))
|
|
135
|
+
out.push(...this.readSegmentFile(path.join(this.d.stateDir, f)));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch { /* @silent-fallback-ok — no state dir → no entries */ }
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Verify a specific {featureId, runId}: the entry exists, the artifact is on
|
|
143
|
+
* disk, its recomputed content hash matches the entry (tamper check), and the
|
|
144
|
+
* signature verifies. This is what the gate calls — the transcript is never the
|
|
145
|
+
* evidence.
|
|
146
|
+
*/
|
|
147
|
+
verifyEntry(featureId, runId) {
|
|
148
|
+
const entry = this.allEntries().find((e) => e.featureId === featureId && e.runId === runId);
|
|
149
|
+
if (!entry)
|
|
150
|
+
return { ok: false, reason: 'no-entry' };
|
|
151
|
+
const artifact = this.readArtifact(featureId, runId);
|
|
152
|
+
if (!artifact)
|
|
153
|
+
return { ok: false, reason: 'artifact-missing', entry };
|
|
154
|
+
const recomputed = sha256(canonicalize(artifact));
|
|
155
|
+
if (recomputed !== entry.contentHash)
|
|
156
|
+
return { ok: false, reason: 'hash-mismatch', entry, artifact };
|
|
157
|
+
if (!this.d.verify(entry.contentHash, entry.signature))
|
|
158
|
+
return { ok: false, reason: 'bad-signature', entry, artifact };
|
|
159
|
+
return { ok: true, entry, artifact };
|
|
160
|
+
}
|
|
161
|
+
/** Verify THIS machine's segment hash-chain (a break = tamper signal, §4.4). */
|
|
162
|
+
verifyOwnChain() {
|
|
163
|
+
const seg = this.readOwnSegment();
|
|
164
|
+
for (let i = 1; i < seg.length; i++) {
|
|
165
|
+
const expectedPrev = sha256(canonicalize(seg[i - 1]));
|
|
166
|
+
if (seg[i].prevEntryHash !== expectedPrev)
|
|
167
|
+
return { ok: false, brokenAtIndex: i };
|
|
168
|
+
}
|
|
169
|
+
return { ok: true };
|
|
170
|
+
}
|
|
171
|
+
/** The freshest VERIFIED entry for a feature across all segments (or null). */
|
|
172
|
+
latestVerified(featureId) {
|
|
173
|
+
const entries = this.allEntries()
|
|
174
|
+
.filter((e) => e.featureId === featureId)
|
|
175
|
+
.sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
|
|
176
|
+
for (const e of entries) {
|
|
177
|
+
const v = this.verifyEntry(e.featureId, e.runId);
|
|
178
|
+
if (v.ok)
|
|
179
|
+
return v;
|
|
180
|
+
}
|
|
181
|
+
return entries.length ? this.verifyEntry(entries[0].featureId, entries[0].runId) : null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=LiveTestArtifactStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveTestArtifactStore.js","sourceRoot":"","sources":["../../src/core/LiveTestArtifactStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveTestGate — the deterministic completion-gate veto (spec
|
|
3
|
+
* docs/specs/live-user-channel-proof-standard.md §4). It refuses a "done"/"shipped"
|
|
4
|
+
* verdict for a USER-FACING feature unless a verified live-test artifact (§4.4)
|
|
5
|
+
* proves it ran through its real surface (Telegram AND Slack for a channel feature)
|
|
6
|
+
* across the required risk categories.
|
|
7
|
+
*
|
|
8
|
+
* Signal vs. Authority (§4.2 — the load-bearing placement): the keyword classifier
|
|
9
|
+
* holds NO standalone blocking authority. The HARD veto rests only on OBJECTIVE
|
|
10
|
+
* facts — an author-DECLARED `userFacing:true` AND the absence of a verified
|
|
11
|
+
* artifact. An undeclared-but-user-facing-looking goal gets a SOFT 'nudge'
|
|
12
|
+
* (return-to-work signal), never a hard block on a brittle guess. Both outcomes
|
|
13
|
+
* keep an autonomous run working (they prevent a premature "done"); only the
|
|
14
|
+
* REPORTED authority differs, so the structure is honest about which is objective.
|
|
15
|
+
*
|
|
16
|
+
* Rollout (§4.8): `mode` is dry-run → warn → veto. In dry-run/warn the decision is
|
|
17
|
+
* computed + logged but `blocks` is false (it never actually stops the run); only
|
|
18
|
+
* `veto` mode blocks. The SAFE direction on an unverifiable artifact is not-proven
|
|
19
|
+
* (return to work), never a false "done".
|
|
20
|
+
*/
|
|
21
|
+
import type { LiveTestArtifactStore, Surface, LiveTestArtifact } from './LiveTestArtifactStore.js';
|
|
22
|
+
export type LiveTestGateMode = 'dry-run' | 'warn' | 'veto';
|
|
23
|
+
export declare function looksUserFacing(goalText: string): boolean;
|
|
24
|
+
export interface LiveTestGateInput {
|
|
25
|
+
featureId: string;
|
|
26
|
+
/** Author declaration; undefined → classifier signal (never a hard block). */
|
|
27
|
+
userFacing?: boolean;
|
|
28
|
+
goalText: string;
|
|
29
|
+
/** The surfaces this feature has (channel/dashboard). Default ['telegram','slack']. */
|
|
30
|
+
requiredSurfaces?: Surface[];
|
|
31
|
+
mode: LiveTestGateMode;
|
|
32
|
+
}
|
|
33
|
+
export type GateOutcome = 'allow' | 'veto' | 'nudge';
|
|
34
|
+
export interface LiveTestGateResult {
|
|
35
|
+
outcome: GateOutcome;
|
|
36
|
+
/** Whether this actually stops the run (only outcome!=allow AND mode==='veto'). */
|
|
37
|
+
blocks: boolean;
|
|
38
|
+
mode: LiveTestGateMode;
|
|
39
|
+
reason: string;
|
|
40
|
+
/** True when the gate WOULD veto/nudge but mode held it (dry-run/warn telemetry). */
|
|
41
|
+
wouldBlock: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Evaluate the live-test artifact for a feature against the §4.5 surface +
|
|
45
|
+
* §4.6 risk-category requirements. Returns null when the artifact fully satisfies
|
|
46
|
+
* the bar, or a human reason string when it does not.
|
|
47
|
+
*/
|
|
48
|
+
export declare function evaluateArtifact(artifact: LiveTestArtifact, requiredSurfaces: Surface[]): string | null;
|
|
49
|
+
export declare class LiveTestGate {
|
|
50
|
+
private readonly store;
|
|
51
|
+
constructor(store: LiveTestArtifactStore);
|
|
52
|
+
evaluate(input: LiveTestGateInput): LiveTestGateResult;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=LiveTestGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveTestGate.d.ts","sourceRoot":"","sources":["../../src/core/LiveTestGate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,OAAO,EAAgB,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAGjH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAQ3D,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,gBAAgB,CAAC,EAAE,OAAO,EAAE,CAAC;IAC7B,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,WAAW,CAAC;IACrB,mFAAmF;IACnF,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,UAAU,EAAE,OAAO,CAAC;CACrB;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,IAAI,CAsCvG;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,qBAAqB;IAEzD,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,kBAAkB;CAmCvD"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveTestGate — the deterministic completion-gate veto (spec
|
|
3
|
+
* docs/specs/live-user-channel-proof-standard.md §4). It refuses a "done"/"shipped"
|
|
4
|
+
* verdict for a USER-FACING feature unless a verified live-test artifact (§4.4)
|
|
5
|
+
* proves it ran through its real surface (Telegram AND Slack for a channel feature)
|
|
6
|
+
* across the required risk categories.
|
|
7
|
+
*
|
|
8
|
+
* Signal vs. Authority (§4.2 — the load-bearing placement): the keyword classifier
|
|
9
|
+
* holds NO standalone blocking authority. The HARD veto rests only on OBJECTIVE
|
|
10
|
+
* facts — an author-DECLARED `userFacing:true` AND the absence of a verified
|
|
11
|
+
* artifact. An undeclared-but-user-facing-looking goal gets a SOFT 'nudge'
|
|
12
|
+
* (return-to-work signal), never a hard block on a brittle guess. Both outcomes
|
|
13
|
+
* keep an autonomous run working (they prevent a premature "done"); only the
|
|
14
|
+
* REPORTED authority differs, so the structure is honest about which is objective.
|
|
15
|
+
*
|
|
16
|
+
* Rollout (§4.8): `mode` is dry-run → warn → veto. In dry-run/warn the decision is
|
|
17
|
+
* computed + logged but `blocks` is false (it never actually stops the run); only
|
|
18
|
+
* `veto` mode blocks. The SAFE direction on an unverifiable artifact is not-proven
|
|
19
|
+
* (return to work), never a false "done".
|
|
20
|
+
*/
|
|
21
|
+
import { REQUIRED_RISK_CATEGORIES } from './LiveTestArtifactStore.js';
|
|
22
|
+
/** Keyword signal that a goal looks user-facing (NOT authority — §4.2/§4.3). */
|
|
23
|
+
const USER_FACING_KEYWORDS = [
|
|
24
|
+
'channel', 'dashboard', 'message', 'transfer', 'slack', 'telegram', 'ux',
|
|
25
|
+
'reply', 'notification', 'notify', 'conversation', 'topic', 'session',
|
|
26
|
+
];
|
|
27
|
+
export function looksUserFacing(goalText) {
|
|
28
|
+
const t = ` ${goalText.toLowerCase()} `;
|
|
29
|
+
return USER_FACING_KEYWORDS.some((k) => new RegExp(`\\b${k}\\b`).test(t));
|
|
30
|
+
}
|
|
31
|
+
function uniq(xs) { return [...new Set(xs)]; }
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate the live-test artifact for a feature against the §4.5 surface +
|
|
34
|
+
* §4.6 risk-category requirements. Returns null when the artifact fully satisfies
|
|
35
|
+
* the bar, or a human reason string when it does not.
|
|
36
|
+
*/
|
|
37
|
+
export function evaluateArtifact(artifact, requiredSurfaces) {
|
|
38
|
+
// §4.5 surfaces: every required surface must be exercised (or carry an audited
|
|
39
|
+
// exemption — modeled as the surface simply being present in surfaces[]).
|
|
40
|
+
const present = new Set(artifact.surfaces);
|
|
41
|
+
const missingSurface = requiredSurfaces.find((s) => !present.has(s));
|
|
42
|
+
if (missingSurface)
|
|
43
|
+
return `missing required surface "${missingSurface}" (have: ${[...present].join(', ') || 'none'})`;
|
|
44
|
+
// §4.4 poison rule + §4.6 BLOCKED taxonomy, per risk category.
|
|
45
|
+
const byCategory = new Map();
|
|
46
|
+
for (const row of artifact.scenarios) {
|
|
47
|
+
const c = byCategory.get(row.riskCategory) ?? { pass: 0, fail: 0, blockedReal: 0 };
|
|
48
|
+
if (row.verdict === 'PASS')
|
|
49
|
+
c.pass++;
|
|
50
|
+
else if (row.verdict === 'FAIL')
|
|
51
|
+
c.fail++;
|
|
52
|
+
else if (row.verdict === 'BLOCKED') {
|
|
53
|
+
// BLOCKED is honored only with a machine-verifiable external blocker (§4.6);
|
|
54
|
+
// a bare BLOCKED counts as FAIL.
|
|
55
|
+
const real = row.blockedKind === 'platform-error' || row.blockedKind === 'platform-outage' || row.blockedKind === 'operator-waiver';
|
|
56
|
+
if (real)
|
|
57
|
+
c.blockedReal++;
|
|
58
|
+
else
|
|
59
|
+
c.fail++;
|
|
60
|
+
}
|
|
61
|
+
byCategory.set(row.riskCategory, c);
|
|
62
|
+
}
|
|
63
|
+
// The load-bearing categories must have a PASS (BLOCKED never satisfies them, §4.6).
|
|
64
|
+
const loadBearing = ['happy-path', 'channel-parity'];
|
|
65
|
+
for (const cat of loadBearing) {
|
|
66
|
+
const c = byCategory.get(cat);
|
|
67
|
+
if (!c || c.pass < 1)
|
|
68
|
+
return `load-bearing category "${cat}" has no PASS scenario`;
|
|
69
|
+
if (c.fail > 0)
|
|
70
|
+
return `category "${cat}" has ${c.fail} FAIL/poisoned scenario(s)`;
|
|
71
|
+
}
|
|
72
|
+
// Every applicable required category present with at least one PASS (§4.6).
|
|
73
|
+
const declared = uniq(artifact.riskCategories);
|
|
74
|
+
for (const cat of declared) {
|
|
75
|
+
if (!REQUIRED_RISK_CATEGORIES.includes(cat))
|
|
76
|
+
continue;
|
|
77
|
+
const c = byCategory.get(cat);
|
|
78
|
+
if (!c || c.pass < 1)
|
|
79
|
+
return `risk category "${cat}" is declared but has no PASS scenario`;
|
|
80
|
+
if (c.fail > 0)
|
|
81
|
+
return `risk category "${cat}" has ${c.fail} FAIL/poisoned scenario(s)`;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
export class LiveTestGate {
|
|
86
|
+
store;
|
|
87
|
+
constructor(store) {
|
|
88
|
+
this.store = store;
|
|
89
|
+
}
|
|
90
|
+
evaluate(input) {
|
|
91
|
+
const requiredSurfaces = input.requiredSurfaces ?? ['telegram', 'slack'];
|
|
92
|
+
const mk = (outcome, reason) => {
|
|
93
|
+
const wouldBlock = outcome !== 'allow';
|
|
94
|
+
return { outcome, reason, mode: input.mode, wouldBlock, blocks: wouldBlock && input.mode === 'veto' };
|
|
95
|
+
};
|
|
96
|
+
// §4.2/§4.3: determine user-facing. Explicit declaration is authoritative;
|
|
97
|
+
// otherwise the classifier is a SIGNAL only.
|
|
98
|
+
const declaredUserFacing = input.userFacing;
|
|
99
|
+
const classifierSaysUserFacing = looksUserFacing(input.goalText);
|
|
100
|
+
if (declaredUserFacing === false) {
|
|
101
|
+
// Declared internal. (A surfaced contradiction is the harness/sentinel's job,
|
|
102
|
+
// signal-only — the gate honors the declaration here.)
|
|
103
|
+
return mk('allow', 'declared userFacing:false — gate not applicable');
|
|
104
|
+
}
|
|
105
|
+
const isUserFacing = declaredUserFacing === true || classifierSaysUserFacing;
|
|
106
|
+
if (!isUserFacing)
|
|
107
|
+
return mk('allow', 'not a user-facing feature (no declaration, classifier negative)');
|
|
108
|
+
// User-facing → require a verified artifact.
|
|
109
|
+
const verified = this.store.latestVerified(input.featureId);
|
|
110
|
+
if (!verified || !verified.ok) {
|
|
111
|
+
const reason = `no verified live-test artifact for "${input.featureId}" (${verified?.reason ?? 'none found'}) — run the user-role harness through ${requiredSurfaces.join(' AND ')} first`;
|
|
112
|
+
// HARD veto only when DECLARED user-facing (objective). Undeclared-but-classified
|
|
113
|
+
// → soft nudge (Signal vs. Authority §4.2).
|
|
114
|
+
return mk(declaredUserFacing === true ? 'veto' : 'nudge', reason);
|
|
115
|
+
}
|
|
116
|
+
const shortfall = evaluateArtifact(verified.artifact, requiredSurfaces);
|
|
117
|
+
if (shortfall) {
|
|
118
|
+
return mk(declaredUserFacing === true ? 'veto' : 'nudge', `live-test artifact present but incomplete: ${shortfall}`);
|
|
119
|
+
}
|
|
120
|
+
return mk('allow', `verified live-test artifact proves "${input.featureId}" across ${requiredSurfaces.join(' AND ')}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// (CMT-1568 live-user-channel-proof — decision-audit record trigger)
|
|
124
|
+
//# sourceMappingURL=LiveTestGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveTestGate.js","sourceRoot":"","sources":["../../src/core/LiveTestGate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveTestHarness — the user-role live-test runner (spec
|
|
3
|
+
* docs/specs/live-user-channel-proof-standard.md §5). It drives a feature
|
|
4
|
+
* end-to-end AS THE USER through the real surface (Telegram AND Slack for a channel
|
|
5
|
+
* feature), recording a signed PASS/FAIL scenario matrix as the artifact the
|
|
6
|
+
* completion gate reads.
|
|
7
|
+
*
|
|
8
|
+
* This module is the harness CORE: it runs a scenario matrix over an INJECTED
|
|
9
|
+
* `ChannelDriver` (send / awaitReply / isDemoChannel), so it is unit-testable with a
|
|
10
|
+
* fake driver. The REAL Telegram + Slack drivers (real-account drive, §5.4, demo
|
|
11
|
+
* channels) and the Playwright dashboard driver wire into this same interface.
|
|
12
|
+
*
|
|
13
|
+
* Structural safety (§5.3): a volatile/permission scenario is REFUSED before any
|
|
14
|
+
* send unless its target channel is a demo channel — a structural throw, not a
|
|
15
|
+
* convention, so a destructive scenario can never touch the live operator channel.
|
|
16
|
+
*
|
|
17
|
+
* Determinism (§5.6/§4.4): the PASS/FAIL verdict rests on DETERMINISTIC protocol
|
|
18
|
+
* evidence (the reply text + the responder machine id captured from the real
|
|
19
|
+
* platform), not a fuzzy judgment. (An optional Tier-1 semantic supervisor for
|
|
20
|
+
* natural-language expectations is a follow-on; the core encodes exact checks.)
|
|
21
|
+
*/
|
|
22
|
+
import type { LiveTestArtifact, LiveTestArtifactStore, RiskCategory, Surface } from './LiveTestArtifactStore.js';
|
|
23
|
+
export interface SendResult {
|
|
24
|
+
messageId: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ReplyResult {
|
|
27
|
+
text: string;
|
|
28
|
+
messageId: string;
|
|
29
|
+
responderMachineId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** The injected channel transport — a real adapter (Telegram/Slack) OR a fake. */
|
|
32
|
+
export interface ChannelDriver {
|
|
33
|
+
/** Whether (surface, channelId) is a registered demo channel (§5.3 isolation). */
|
|
34
|
+
isDemoChannel(surface: Surface, channelId: string): boolean;
|
|
35
|
+
/** Send a user message on the real surface. */
|
|
36
|
+
send(surface: Surface, channelId: string, text: string): Promise<SendResult>;
|
|
37
|
+
/** Await the agent's reply (resolves null on timeout). */
|
|
38
|
+
awaitReply(surface: Surface, channelId: string, opts: {
|
|
39
|
+
timeoutMs: number;
|
|
40
|
+
afterMessageId?: string;
|
|
41
|
+
}): Promise<ReplyResult | null>;
|
|
42
|
+
}
|
|
43
|
+
export type Volatility = 'safe' | 'volatile' | 'permission';
|
|
44
|
+
export interface HarnessScenario {
|
|
45
|
+
id: string;
|
|
46
|
+
description: string;
|
|
47
|
+
surface: Surface;
|
|
48
|
+
riskCategory: RiskCategory;
|
|
49
|
+
volatility: Volatility;
|
|
50
|
+
channelId: string;
|
|
51
|
+
/** The user message to send. */
|
|
52
|
+
input: string;
|
|
53
|
+
/** Deterministic expectations checked against the captured reply. */
|
|
54
|
+
expect: {
|
|
55
|
+
replyContains?: string;
|
|
56
|
+
replyNotEmpty?: boolean;
|
|
57
|
+
responderMachine?: string;
|
|
58
|
+
};
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface HarnessMatrix {
|
|
62
|
+
featureId: string;
|
|
63
|
+
surfaces: Surface[];
|
|
64
|
+
riskCategories: RiskCategory[];
|
|
65
|
+
scenarios: HarnessScenario[];
|
|
66
|
+
}
|
|
67
|
+
export interface LiveTestHarnessDeps {
|
|
68
|
+
store: LiveTestArtifactStore;
|
|
69
|
+
driver: ChannelDriver;
|
|
70
|
+
runnerFingerprint: string;
|
|
71
|
+
/** Bounded retries on a reply timeout before recording FAIL (§5.5 flake mgmt). */
|
|
72
|
+
maxReplyRetries?: number;
|
|
73
|
+
defaultTimeoutMs?: number;
|
|
74
|
+
now?: () => number;
|
|
75
|
+
logger?: (m: string) => void;
|
|
76
|
+
}
|
|
77
|
+
export declare class HarnessVolatileChannelError extends Error {
|
|
78
|
+
constructor(scenarioId: string, surface: Surface, channelId: string);
|
|
79
|
+
}
|
|
80
|
+
export declare class LiveTestHarness {
|
|
81
|
+
private readonly d;
|
|
82
|
+
constructor(deps: LiveTestHarnessDeps);
|
|
83
|
+
private now;
|
|
84
|
+
private log;
|
|
85
|
+
/**
|
|
86
|
+
* Run the scenario matrix and write the signed artifact. `runId` lets the caller
|
|
87
|
+
* pin a stable id (defaults to a now-stamped id). THROWS HarnessVolatileChannelError
|
|
88
|
+
* if any volatile/permission scenario targets a non-demo channel (fail-fast, before
|
|
89
|
+
* any send) — the §5.3 structural guard.
|
|
90
|
+
*/
|
|
91
|
+
run(matrix: HarnessMatrix, opts?: {
|
|
92
|
+
runId?: string;
|
|
93
|
+
}): Promise<{
|
|
94
|
+
artifact: LiveTestArtifact;
|
|
95
|
+
entry: ReturnType<LiveTestArtifactStore['write']>;
|
|
96
|
+
}>;
|
|
97
|
+
private runScenario;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=LiveTestHarness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveTestHarness.d.ts","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAAE,qBAAqB,EAAE,YAAY,EAAe,OAAO,EAC5E,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,UAAU;IAAG,SAAS,EAAE,MAAM,CAAA;CAAE;AACjD,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE;AAE7F,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,kFAAkF;IAClF,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5D,+CAA+C;IAC/C,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7E,0DAA0D;IAC1D,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACpI;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,MAAM,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,cAAc,EAAE,YAAY,EAAE,CAAC;IAC/B,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM;CAIpE;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAsB;gBAE5B,IAAI,EAAE,mBAAmB;IAIrC,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,GAAG;IAEX;;;;;OAKG;IACG,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,gBAAgB,CAAC;QAAC,KAAK,EAAE,UAAU,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC;YA4B7I,WAAW;CA+B1B"}
|