@starci/hfs 4.12.2 → 4.13.2

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.
Files changed (160) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -9
  3. package/cache/app-families.d.mts +30 -0
  4. package/cache/app-families.mjs +94 -0
  5. package/cache/content-index.d.mts +13 -0
  6. package/cache/content-index.mjs +37 -0
  7. package/cache/db-family.d.mts +13 -0
  8. package/cache/db-family.mjs +37 -0
  9. package/cache/fake-session.d.mts +42 -0
  10. package/cache/fake-session.mjs +57 -0
  11. package/cache/fingerprint.d.mts +28 -0
  12. package/cache/fingerprint.mjs +120 -0
  13. package/cache/format-family.d.mts +13 -0
  14. package/cache/format-family.mjs +49 -0
  15. package/cache/git.d.mts +11 -0
  16. package/cache/git.mjs +13 -0
  17. package/cache/hash.d.mts +15 -0
  18. package/cache/hash.mjs +58 -0
  19. package/cache/hfs-family.d.mts +35 -0
  20. package/cache/hfs-family.mjs +164 -0
  21. package/cache/import-graph.d.mts +75 -0
  22. package/cache/import-graph.mjs +204 -0
  23. package/cache/jsonl.d.mts +31 -0
  24. package/cache/jsonl.mjs +115 -0
  25. package/cache/lint-family.d.mts +162 -0
  26. package/cache/lint-family.mjs +433 -0
  27. package/cache/order.d.mts +1 -0
  28. package/cache/order.mjs +2 -0
  29. package/cache/progress.d.mts +19 -0
  30. package/cache/progress.mjs +68 -0
  31. package/cache/quality-baselines.d.mts +109 -0
  32. package/cache/quality-baselines.mjs +235 -0
  33. package/cache/records.d.mts +33 -0
  34. package/cache/records.mjs +54 -0
  35. package/cache/report.d.mts +19 -0
  36. package/cache/report.mjs +32 -0
  37. package/cache/run-log.d.mts +17 -0
  38. package/cache/run-log.mjs +37 -0
  39. package/cache/run.d.mts +33 -0
  40. package/cache/run.mjs +65 -0
  41. package/cache/session.d.mts +29 -0
  42. package/cache/session.mjs +197 -0
  43. package/cache/side-key.d.mts +10 -0
  44. package/cache/side-key.mjs +47 -0
  45. package/cache/test-family.d.mts +130 -0
  46. package/cache/test-family.mjs +258 -0
  47. package/cache/trust.d.mts +53 -0
  48. package/cache/trust.mjs +157 -0
  49. package/cache/types-family.d.mts +74 -0
  50. package/cache/types-family.mjs +154 -0
  51. package/cache/types.d.mts +99 -0
  52. package/cache/types.mjs +8 -0
  53. package/cache/wiring-family.d.mts +14 -0
  54. package/cache/wiring-family.mjs +43 -0
  55. package/coverage/cli.d.mts +4 -2
  56. package/coverage/cli.mjs +44 -5
  57. package/db/migrate.d.mts +16 -0
  58. package/db/migrate.mjs +21 -4
  59. package/db/postgres.d.mts +23 -0
  60. package/db/postgres.mjs +32 -1
  61. package/lint/run.d.mts +47 -1
  62. package/lint/run.mjs +157 -5
  63. package/package.json +2 -1
  64. package/release/gate.d.mts +2 -0
  65. package/release/gate.mjs +4 -2
  66. package/release/records.d.mts +15 -0
  67. package/release/records.mjs +25 -0
  68. package/release/urgent.mjs +23 -4
  69. package/runtime/knowledge/grammars/core/playbook.yaml +12 -18
  70. package/runtime/knowledge/hfs/canon-pins.yaml +8 -8
  71. package/runtime/knowledge/hfs/ci-standard.yaml +29 -6
  72. package/runtime/knowledge/hfs/rules.yaml +97 -7
  73. package/runtime/knowledge/hfs/slots.yaml +20 -8
  74. package/runtime/modules/kernel/failure-codes.yaml +46 -4
  75. package/runtime/modules/schemas/application-stacks.schema.yaml +48 -0
  76. package/runtime/scripts/hfs/architecture/hfs.d.mts +6 -6
  77. package/runtime/scripts/hfs/check.mjs +1 -1
  78. package/runtime/scripts/hfs/gate-threshold.mjs +2 -1
  79. package/runtime/scripts/hfs/rules/boot-proof.d.mts +5 -0
  80. package/runtime/scripts/hfs/rules/boot-proof.mjs +15 -1
  81. package/runtime/scripts/hfs/rules/ci-custody.mjs +3 -1
  82. package/runtime/scripts/hfs/rules/ci-standard.mjs +32 -3
  83. package/runtime/scripts/hfs/rules/database-migrations.d.mts +14 -0
  84. package/runtime/scripts/hfs/rules/database-migrations.mjs +55 -0
  85. package/runtime/scripts/hfs/rules/database.mjs +2 -1
  86. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +53 -6
  87. package/runtime/scripts/hfs/rules/release-provenance.mjs +32 -4
  88. package/runtime/scripts/hfs/rules/stacks.mjs +5 -1
  89. package/runtime/scripts/hfs/rules/supply-chain.mjs +4 -1
  90. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +98 -7
  91. package/runtime/scripts/hfs/rules/unused-symbol.d.mts +9 -2
  92. package/runtime/scripts/hfs/rules/unused-symbol.mjs +15 -2
  93. package/runtime/scripts/hfs/runtime-rules/git-triggers.mjs +21 -5
  94. package/runtime/scripts/hfs/stack-deploy-config.d.mts +18 -0
  95. package/runtime/scripts/hfs/stack-deploy-config.mjs +53 -1
  96. package/runtime/scripts/hfs/stacks-gate.mjs +2 -0
  97. package/runtime/scripts/hfs/tree-findings.mjs +3 -1
  98. package/scaffold/add-inbox-lite.mjs +2 -4
  99. package/scaffold/add-queue-lite.mjs +2 -3
  100. package/scaffold/add-table.mjs +2 -4
  101. package/scaffold/migration-stamp.d.mts +11 -0
  102. package/scaffold/migration-stamp.mjs +32 -0
  103. package/src/helpers/hfs-arch-fixture.d.mts +1 -1
  104. package/src/main.d.mts +4 -1
  105. package/src/main.mjs +189 -74
  106. package/src/stack-records-double.d.mts +33 -0
  107. package/stack/ci-key.d.mts +11 -0
  108. package/stack/ci-key.mjs +44 -0
  109. package/stack/cli.d.mts +7 -1
  110. package/stack/cli.mjs +22 -13
  111. package/stack/config.d.mts +15 -0
  112. package/stack/converge.d.mts +2 -0
  113. package/stack/converge.mjs +1 -1
  114. package/stack/db-phases.d.mts +185 -0
  115. package/stack/db-phases.mjs +346 -0
  116. package/stack/deploy.d.mts +22 -5
  117. package/stack/deploy.mjs +242 -72
  118. package/stack/journal.d.mts +146 -0
  119. package/stack/journal.mjs +191 -0
  120. package/stack/manager-backup.d.mts +54 -0
  121. package/stack/manager-backup.mjs +136 -0
  122. package/stack/notify.d.mts +9 -0
  123. package/stack/notify.mjs +32 -0
  124. package/stack/release-store.d.mts +49 -1
  125. package/stack/release-store.mjs +102 -16
  126. package/stack/remote.d.mts +2 -0
  127. package/stack/remote.mjs +32 -11
  128. package/stack/rollback-guard.d.mts +10 -0
  129. package/stack/rollback-guard.mjs +41 -0
  130. package/stack/server-key.d.mts +2 -0
  131. package/stack/server-key.mjs +1 -1
  132. package/stack/ssh-gate.d.mts +35 -0
  133. package/stack/ssh-gate.mjs +145 -0
  134. package/stack/status.d.mts +51 -0
  135. package/stack/status.mjs +115 -0
  136. package/stack/urgent-floor.d.mts +10 -61
  137. package/stack/urgent-floor.mjs +28 -123
  138. package/stack/verify.mjs +26 -0
  139. package/sync/ci-commands.mjs +10 -5
  140. package/sync/format-worker.d.mts +1 -0
  141. package/sync/format-worker.mjs +17 -0
  142. package/sync/format.d.mts +21 -3
  143. package/sync/format.mjs +99 -7
  144. package/sync/index.d.mts +3 -6
  145. package/sync/index.mjs +12 -12
  146. package/sync/stack-images.d.mts +25 -10
  147. package/sync/stack-images.mjs +215 -54
  148. package/templates/app/ci-workflows/github/workflows/ci.yml +114 -7
  149. package/templates/app/ci-workflows/github/workflows/images.yml +21 -65
  150. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +130 -37
  151. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +114 -7
  152. package/templates/app/ci-workflows-lite/github/workflows/images.yml +21 -65
  153. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +130 -37
  154. package/templates/app/gitignore +1 -0
  155. package/templates/app/gitignore-lite +1 -0
  156. package/templates/app/package-scripts/db.json +1 -1
  157. package/templates/app/skeleton/.gitattributes +2 -0
  158. package/templates/app/skeleton-lite/.gitattributes +2 -0
  159. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +2 -1
  160. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +0 -63
@@ -0,0 +1,31 @@
1
+ import type { CacheRecord, Finding, JsonValue } from './types.mjs';
2
+ /** `value` as JSON with every object's keys sorted and undefined members dropped. */
3
+ export declare function canonicalJson(value: unknown): string;
4
+ /** Findings sorted by line, column, rule, message. */
5
+ export declare function sortFindings(findings: readonly Finding[]): Finding[];
6
+ /** Records sorted by path, then step. */
7
+ export declare function sortRecords(records: readonly CacheRecord[]): CacheRecord[];
8
+ /** The file text of `records`: sorted, one canonical line each, LF, a final newline; the empty set is the empty text. */
9
+ export declare function serializeRecords(records: readonly CacheRecord[]): string;
10
+ /** What a file held: the records it parsed and how many lines were not one (a half-merged or hand-damaged line). */
11
+ export interface ParsedRecords {
12
+ readonly records: CacheRecord[];
13
+ readonly invalid: number;
14
+ }
15
+ /** Parses a record file. CRLF is tolerated; a line that is not a record is counted and skipped, never thrown on. */
16
+ export declare function parseRecords(text: string): ParsedRecords;
17
+ /**
18
+ * The records a union merge may have doubled, reduced to one per (path, step): the one whose hash matches the tree
19
+ * (`currentHash(path)`); the rest are dropped. A key whose matching records differ keeps none (two branches disagreeing is a
20
+ * mismatch, so the step judges again).
21
+ */
22
+ export declare function reconcile(records: readonly CacheRecord[], currentHash: (path: string) => string | undefined): CacheRecord[];
23
+ /**
24
+ * The area a record belongs to: the first `depth` folders of its path (`modules/billing` for `modules/billing/a/b.ts`), or
25
+ * `root` for a file with no folder. A change rewrites only the area files it touched.
26
+ */
27
+ export declare function areaOf(path: string, depth?: number): string;
28
+ /** Records grouped by area file name (`<area>.jsonl`), groups and files sorted. */
29
+ export declare function groupByArea(records: readonly CacheRecord[], depth?: number): Map<string, CacheRecord[]>;
30
+ /** True when `value` survives JSON unchanged: a guard for record payloads. */
31
+ export declare function isJsonValue(value: unknown): value is JsonValue;
@@ -0,0 +1,115 @@
1
+ function compare(a, b) {
2
+ return a < b ? -1 : a > b ? 1 : 0;
3
+ }
4
+ function sortKeys(value) {
5
+ if (Array.isArray(value))
6
+ return value.map(sortKeys);
7
+ if (value !== null && typeof value === 'object') {
8
+ const source = value;
9
+ const out = {};
10
+ for (const key of Object.keys(source).sort(compare))
11
+ if (source[key] !== undefined)
12
+ out[key] = sortKeys(source[key]);
13
+ return out;
14
+ }
15
+ return value;
16
+ }
17
+ /** `value` as JSON with every object's keys sorted and undefined members dropped. */
18
+ export function canonicalJson(value) {
19
+ return JSON.stringify(sortKeys(value));
20
+ }
21
+ /** Findings sorted by line, column, rule, message. */
22
+ export function sortFindings(findings) {
23
+ return [...findings].sort((a, b) => a.line - b.line || a.column - b.column || compare(a.rule, b.rule) || compare(a.message, b.message));
24
+ }
25
+ /** Records sorted by path, then step. */
26
+ export function sortRecords(records) {
27
+ return [...records].sort((a, b) => compare(a.path, b.path) || compare(a.step, b.step));
28
+ }
29
+ /** The file text of `records`: sorted, one canonical line each, LF, a final newline; the empty set is the empty text. */
30
+ export function serializeRecords(records) {
31
+ const lines = sortRecords(records).map((record) => canonicalJson({ ...record, findings: sortFindings(record.findings) }));
32
+ return lines.length === 0 ? '' : `${lines.join('\n')}\n`;
33
+ }
34
+ function isRecord(value) {
35
+ if (value === null || typeof value !== 'object')
36
+ return false;
37
+ const v = value;
38
+ return typeof v['path'] === 'string' && typeof v['step'] === 'string' && typeof v['hash'] === 'string' && Array.isArray(v['findings']);
39
+ }
40
+ /** Parses a record file. CRLF is tolerated; a line that is not a record is counted and skipped, never thrown on. */
41
+ export function parseRecords(text) {
42
+ const records = [];
43
+ let invalid = 0;
44
+ for (const raw of text.split('\n')) {
45
+ const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw;
46
+ if (line.trim() === '')
47
+ continue;
48
+ try {
49
+ const parsed = JSON.parse(line);
50
+ if (isRecord(parsed))
51
+ records.push(parsed);
52
+ else
53
+ invalid += 1;
54
+ }
55
+ catch {
56
+ invalid += 1;
57
+ }
58
+ }
59
+ return { records, invalid };
60
+ }
61
+ /**
62
+ * The records a union merge may have doubled, reduced to one per (path, step): the one whose hash matches the tree
63
+ * (`currentHash(path)`); the rest are dropped. A key whose matching records differ keeps none (two branches disagreeing is a
64
+ * mismatch, so the step judges again).
65
+ */
66
+ export function reconcile(records, currentHash) {
67
+ const byKey = new Map();
68
+ for (const record of records) {
69
+ if (currentHash(record.path) !== record.hash)
70
+ continue;
71
+ const key = `${record.path}\0${record.step}`;
72
+ byKey.set(key, [...(byKey.get(key) ?? []), record]);
73
+ }
74
+ const out = [];
75
+ for (const group of byKey.values()) {
76
+ const first = group[0];
77
+ if (first === undefined)
78
+ continue;
79
+ const text = canonicalJson(first);
80
+ if (group.every((record) => canonicalJson(record) === text))
81
+ out.push(first);
82
+ }
83
+ return sortRecords(out);
84
+ }
85
+ /**
86
+ * The area a record belongs to: the first `depth` folders of its path (`modules/billing` for `modules/billing/a/b.ts`), or
87
+ * `root` for a file with no folder. A change rewrites only the area files it touched.
88
+ */
89
+ export function areaOf(path, depth = 2) {
90
+ const folders = path.split('/').slice(0, -1).slice(0, depth);
91
+ return folders.length === 0 ? 'root' : folders.join('/');
92
+ }
93
+ /** Records grouped by area file name (`<area>.jsonl`), groups and files sorted. */
94
+ export function groupByArea(records, depth = 2) {
95
+ const groups = new Map();
96
+ for (const record of records) {
97
+ const file = `${areaOf(record.path, depth)}.jsonl`;
98
+ groups.set(file, [...(groups.get(file) ?? []), record]);
99
+ }
100
+ for (const [file, group] of groups)
101
+ groups.set(file, sortRecords(group));
102
+ return new Map([...groups].sort(([a], [b]) => compare(a, b)));
103
+ }
104
+ /** True when `value` survives JSON unchanged: a guard for record payloads. */
105
+ export function isJsonValue(value) {
106
+ if (value === null || typeof value === 'boolean' || typeof value === 'string')
107
+ return true;
108
+ if (typeof value === 'number')
109
+ return Number.isFinite(value);
110
+ if (Array.isArray(value))
111
+ return value.every(isJsonValue);
112
+ if (typeof value === 'object')
113
+ return Object.values(value).every(isJsonValue);
114
+ return false;
115
+ }
@@ -0,0 +1,162 @@
1
+ import type TypeScript from 'typescript';
2
+ import type { CheckSession, FamilyReport } from './types.mjs';
3
+ type Ts = typeof TypeScript;
4
+ /** Record steps of the `lint/<side>` folder. */
5
+ export declare const LINT_STEPS: Readonly<{
6
+ eslint: "eslint";
7
+ stylelint: "stylelint";
8
+ imports: "imports";
9
+ graph: "graph";
10
+ meta: "meta";
11
+ }>;
12
+ /** The record path of the side's own record (no file of the tree holds it). */
13
+ export declare const SIDE_RECORD = ".side";
14
+ /** One input a rule reads that the import graph does not carry (`crossFileInputs` of a canon; see its cross-file-inputs.ts). */
15
+ export interface CrossFileInput {
16
+ readonly rule: string;
17
+ readonly kind: 'content' | 'exists' | 'git';
18
+ readonly relativeTo: 'file' | 'side';
19
+ readonly globs: readonly string[];
20
+ readonly appliesTo: readonly string[];
21
+ /** For `git`: the line count above which the rule reads history; a shorter file does not depend on it. */
22
+ readonly minLines?: number;
23
+ readonly reader?: string;
24
+ }
25
+ /** One finding of the canon's project graph on a file. */
26
+ export interface GraphFinding {
27
+ readonly rule: string;
28
+ readonly code: string;
29
+ readonly line: number;
30
+ readonly column: number;
31
+ readonly message: string;
32
+ }
33
+ /** What a canon's `projectGraph` export answers. */
34
+ export interface GraphSnapshot {
35
+ readonly files?: number;
36
+ readonly byFile: Readonly<Record<string, readonly GraphFinding[]>>;
37
+ }
38
+ /** What the family needs to know of one installed canon (an ESLint canon, a grammar family, the stylelint canon). */
39
+ export interface CanonDeclarations {
40
+ /** The package name, for the reason line. */
41
+ readonly name: string;
42
+ /** Its `crossFileInputs`, or null when the installed canon does not declare them (the side is then linted whole). */
43
+ readonly crossFileInputs: readonly CrossFileInput[] | null;
44
+ /** Whether the canon publishes project-graph rules. */
45
+ readonly graphRules: boolean;
46
+ /** Its `projectGraph` export; null when it publishes graph rules without the export (the side is then linted whole). */
47
+ readonly projectGraph: ((input: {
48
+ repoRoot: string;
49
+ }) => GraphSnapshot) | null;
50
+ }
51
+ /** The fields of an ESLint result the cache keeps and rebuilds. */
52
+ export interface EslintMessage {
53
+ ruleId: string | null;
54
+ message: string;
55
+ line?: number;
56
+ column?: number;
57
+ endLine?: number;
58
+ severity?: number;
59
+ [key: string]: unknown;
60
+ }
61
+ export interface EslintResult {
62
+ filePath: string;
63
+ messages: EslintMessage[];
64
+ [key: string]: unknown;
65
+ }
66
+ /** The fields of a stylelint result the cache keeps and rebuilds. */
67
+ export interface StylelintWarning {
68
+ rule: string;
69
+ text: string;
70
+ line?: number;
71
+ column?: number;
72
+ endLine?: number;
73
+ severity?: string;
74
+ [key: string]: unknown;
75
+ }
76
+ export interface StylelintResult {
77
+ source: string;
78
+ warnings: StylelintWarning[];
79
+ [key: string]: unknown;
80
+ }
81
+ /** The answer of a linter seam: validated results of the files it was asked about, or why it could not complete. */
82
+ export interface LinterOutcome<R> {
83
+ results: R[];
84
+ errors: string[];
85
+ }
86
+ /** Everything one side's cached lint needs. */
87
+ export interface LintSideInput {
88
+ session: CheckSession;
89
+ side: string;
90
+ /** The absolute side folder, where the linters run from. */
91
+ sideRoot: string;
92
+ /** The canon scope of the side, enumerated afresh: side-relative posix source files. */
93
+ scope: readonly string[];
94
+ /** The stylesheets stylelint judges (fe only): side-relative posix; empty otherwise. */
95
+ stylesheets: readonly string[];
96
+ /** The files this run reports when it reports part of a side (a workspace lint); null reports the whole side. A partial run never writes. */
97
+ report: ((file: string) => boolean) | null;
98
+ /** The ESLint canon, its grammar family, in config order. */
99
+ canons: readonly CanonDeclarations[];
100
+ /** The stylelint canon; null when the side has no stylesheet judge. */
101
+ styleCanon: CanonDeclarations | null;
102
+ /** The app's own TypeScript, for the import graph. */
103
+ typescript: () => Ts;
104
+ /** A digest of the side's untracked program inputs (generated code the program reads); a change dirties the side. */
105
+ extraKey: string;
106
+ /** The merge-base of HEAD with the base branch (what the size-growth rules compare with), or a marker when there is none; `git` inputs depend on it. */
107
+ gitToken?: string;
108
+ /** Set for `--full` or a full decision: ignore the records and rewrite them. */
109
+ full: string | null;
110
+ /** Whether this run may write the side's records. */
111
+ write: boolean;
112
+ /** ESLint over `files` (side-relative), or over the whole requested scope when null; results already validated and with app-relative messages. */
113
+ eslint: (files: readonly string[] | null) => Promise<LinterOutcome<EslintResult>>;
114
+ /** stylelint over `files`, or over the whole glob when null. Absent when the side has no stylesheet judge. */
115
+ stylelint: ((files: readonly string[] | null) => Promise<LinterOutcome<StylelintResult>>) | null;
116
+ }
117
+ /** What one side's lint reports back to the verb. */
118
+ export interface LintSideOutcome {
119
+ eslint: EslintResult[];
120
+ stylelint: StylelintResult[];
121
+ errors: string[];
122
+ stats: LintSideStats;
123
+ }
124
+ /** The counts and the reason of a side, for the family line. */
125
+ export interface LintSideStats {
126
+ mode: 'full' | 'diff';
127
+ reason: string;
128
+ checked: number;
129
+ reused: number;
130
+ newFindings: number;
131
+ knownFindings: number;
132
+ fixedFindings: number;
133
+ written: boolean;
134
+ }
135
+ /**
136
+ * The digest of what the declared cross-file inputs of `file` hold now: for each declaration that applies, the matching paths of the app with
137
+ * their hashes (`content`) or alone (`exists`). Undefined when no declaration applies. `paths` are app-relative; side-relative globs are
138
+ * resolved below `<side>/`, so a glob may step out of the side with `../` to the app root.
139
+ */
140
+ export declare function crossDigestFactory(options: {
141
+ side: string;
142
+ declarations: readonly CrossFileInput[];
143
+ paths: readonly string[];
144
+ hashOf: (appRelative: string) => string | undefined;
145
+ /** The state of git history the `git` inputs stand for (the merge-base with the base branch); changes when the base moves. */
146
+ gitToken?: string;
147
+ /** The line count of a side-relative file, for the `git` inputs that matter only above a size. */
148
+ lineCountOf?: (file: string) => number;
149
+ }): (file: string) => string | undefined;
150
+ /** The TypeScript the app installed, resolved from the side folder like the linters are (not this package's own copy). */
151
+ export declare function loadAppTypeScript(sideRoot: string): Ts;
152
+ /**
153
+ * Judges one side: ESLint (and, for fe, stylelint) over what the records cannot vouch for, the records of the rest, and the new records.
154
+ * Never throws for a tool failure: those come back in `errors`, and a side with an error stores nothing.
155
+ */
156
+ export declare function lintSide(input: LintSideInput): Promise<LintSideOutcome>;
157
+ /** The family line of a run over several sides: one report for the family, the sides' counts added and their reasons joined. */
158
+ export declare function lintFamilyReport(sides: readonly {
159
+ side: string;
160
+ stats: LintSideStats;
161
+ }[]): FamilyReport;
162
+ export {};