@qontinui/shared-types 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/GlossaryTerm.d-orY3JZRJ.d.cts +40 -0
  2. package/dist/GlossaryTerm.d-orY3JZRJ.d.ts +40 -0
  3. package/dist/glossary.cjs +310 -0
  4. package/dist/glossary.cjs.map +1 -0
  5. package/dist/glossary.d.cts +52 -0
  6. package/dist/glossary.d.ts +52 -0
  7. package/dist/glossary.js +277 -0
  8. package/dist/glossary.js.map +1 -0
  9. package/dist/index.d.cts +2 -2
  10. package/dist/index.d.ts +2 -2
  11. package/dist/refusal.cjs +19 -0
  12. package/dist/refusal.cjs.map +1 -0
  13. package/dist/refusal.d.cts +175 -0
  14. package/dist/refusal.d.ts +175 -0
  15. package/dist/refusal.js +1 -0
  16. package/dist/refusal.js.map +1 -0
  17. package/dist/runner.d.cts +1 -1
  18. package/dist/runner.d.ts +1 -1
  19. package/dist/scheduler.d.cts +62 -1
  20. package/dist/scheduler.d.ts +62 -1
  21. package/package.json +11 -1
  22. package/src/generated/ConditionStatus.d.ts +12 -0
  23. package/src/generated/CreateScheduledTaskRequest.d.ts +1 -0
  24. package/src/generated/FrontierEntry.d.ts +62 -0
  25. package/src/generated/GlossaryTerm.d.ts +38 -0
  26. package/src/generated/JourneyEdgeObservation.d.ts +91 -0
  27. package/src/generated/JourneyLedgerHealth.d.ts +21 -0
  28. package/src/generated/JourneyNode.d.ts +72 -0
  29. package/src/generated/JourneyTrigger.d.ts +47 -0
  30. package/src/generated/NextAction.d.ts +37 -0
  31. package/src/generated/NextActionKind.d.ts +26 -0
  32. package/src/generated/ProbeCondition.d.ts +44 -0
  33. package/src/generated/Refusal.d.ts +63 -0
  34. package/src/generated/RefusalCode.d.ts +33 -0
  35. package/src/generated/RefusalSource.d.ts +12 -0
  36. package/src/generated/ScheduleConditions.d.ts +5 -0
  37. package/src/generated/ScheduledTask.d.ts +1 -0
  38. package/src/generated/UpdateScheduledTaskRequest.d.ts +1 -0
  39. package/src/generated/index.ts +12 -0
  40. package/src/glossary/__tests__/glossary.test.ts +46 -0
  41. package/src/glossary/generated.ts +276 -0
  42. package/src/glossary/index.ts +45 -0
  43. package/src/refusal/index.ts +17 -0
  44. package/src/runner/_api.ts +2 -0
  45. package/src/scheduler/_api.ts +1 -0
@@ -0,0 +1,72 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ /**
9
+ * A vertex of the journey graph: one observed configuration of one page.
10
+ *
11
+ * Identity is a deterministic predicate, not a judgment: a node is
12
+ * `(spec_id, sorted active IR state ids)` within an app. When the page has
13
+ * no spec, or no state was classified present, the node is UNMODELLED and is
14
+ * identified by its route template (else its page label) — it still takes
15
+ * part in reachability and is counted as unmodelled in coverage.
16
+ *
17
+ * Build one with [`JourneyNode::new`], which sorts and dedups `state_ids`
18
+ * and derives `modelled`; [`JourneyNode::key`] is the canonical string form
19
+ * stored in `journey_frontier.node_key`.
20
+ *
21
+ * Node identity is `key()` / `node_key` — NEVER jsonb equality of the
22
+ * serialized node (which also carries non-identity detail such as
23
+ * `pageLabel` on a modelled node).
24
+ * Unlike the crate's other optional fields, this struct's optional fields
25
+ * always serialize, as `null` when absent, so every stored node has the same
26
+ * key set.
27
+ */
28
+ export interface JourneyNode {
29
+ /**
30
+ * True iff `spec_id` is present AND `state_ids` is non-empty.
31
+ */
32
+ modelled: boolean;
33
+ /**
34
+ * An APP-DECLARED page identifier — never a URL path, never user input.
35
+ *
36
+ * PRODUCER OBLIGATION (this is the privacy guarantee): fill it only from
37
+ * the snapshot's `page.pageContext.meta.tabId`, then `activeTab`, then
38
+ * `page.pageContext.name`, and NEVER from `page.pathname` in any form,
39
+ * raw or slugged. (The runner's existing `resolve_page_label` in
40
+ * `state_discovery/capture.rs` has a fourth fallback that slugs
41
+ * `page.pathname`; the journey producer must not use that fallback.)
42
+ * `pageContext.name` is a free-form display name (e.g. `"Import /
43
+ * Export"`), so the producer SLUGS it before storing — which leaves a
44
+ * `/` meaning only one thing: a leaked, unslugged path.
45
+ *
46
+ * [`JourneyNode::validate`] rejects a `/` as a TRIPWIRE for that leak.
47
+ * It is not the guarantee: a slugged path contains no `/` and passes.
48
+ */
49
+ pageLabel: string | null;
50
+ /**
51
+ * The framework route PATTERN the snapshot's page was served under
52
+ * (e.g. `/admin/coord/runs/[id]`), when known. Never a concrete URL path:
53
+ * a concrete path can embed user input (`/search/<term>`,
54
+ * `/users/<email>`). Not heuristically validated — the producer must
55
+ * fill it from the router's pattern, not from `location.pathname`.
56
+ */
57
+ pathnameTemplate: string | null;
58
+ /**
59
+ * The page spec the snapshot matched; `None` = no spec for this page.
60
+ * Load-bearing: some state ids are declared on more than one page.
61
+ * Must not contain `#` or start with `unmodelled:` (see [`JourneyNode::key`]).
62
+ */
63
+ specId: string | null;
64
+ /**
65
+ * Ids of the IR states classified present — deduped, in BYTE-WISE
66
+ * (UTF-8) ascending order, i.e. Rust's `str` ordering. Producers in
67
+ * other languages must sort by UTF-8 bytes (not UTF-16 code units, not
68
+ * a locale collation) or their keys will not match. No id may contain
69
+ * `,` (see [`JourneyNode::key`]).
70
+ */
71
+ stateIds: string[];
72
+ }
@@ -0,0 +1,47 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ /**
9
+ * What caused an edge — structurally only.
10
+ *
11
+ * `deny_unknown_fields` and the deliberate absence of any text/value field
12
+ * are the privacy control: a caller cannot smuggle a typed value into the
13
+ * ledger, because no such key deserializes and no such field exists to set.
14
+ */
15
+ export interface JourneyTrigger {
16
+ /**
17
+ * The action verb (`click`, `type`, `select`, a component action id, …).
18
+ */
19
+ actionType: string;
20
+ /**
21
+ * Which runner choke point captured it.
22
+ */
23
+ chokePoint:
24
+ | "element_action"
25
+ | "batch_action"
26
+ | "component_action"
27
+ | "sdk_element_action"
28
+ | "execute_with_diff"
29
+ | "navigation";
30
+ /**
31
+ * The affordance's declared effect; `None` = undeclared (NOT "safe").
32
+ */
33
+ declaredEffect?: ("read" | "write" | "destructive") | null;
34
+ /**
35
+ * How the transition was initiated.
36
+ */
37
+ navigationTrigger:
38
+ "affordance" | "push" | "replace" | "pop" | "initial" | "hash";
39
+ /**
40
+ * Structural fingerprint of the target element; `None` = not resolved.
41
+ */
42
+ targetFingerprint?: string | null;
43
+ /**
44
+ * ARIA role of the target; `None` = not reported.
45
+ */
46
+ targetRole?: string | null;
47
+ }
@@ -0,0 +1,37 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ import type { NextActionKind } from "./NextActionKind";
9
+
10
+ /**
11
+ * The typed next step of a [`Refusal`]. Decoding is lenient toward newer
12
+ * producers (module docs, "Forward compatibility").
13
+ */
14
+ export interface NextAction {
15
+ kind: NextActionKind;
16
+ /**
17
+ * For [`NextActionKind::RetryLater`]: the earliest useful retry, in whole
18
+ * seconds. Absent when no delay is known.
19
+ */
20
+ retry_after_s?: number | null;
21
+ /**
22
+ * What the action applies to: the command to run, the page to open, the
23
+ * setting to set, the gate to wait for. Its meaning is fixed by `kind`.
24
+ * On [`NextActionKind::RetryLater`] it is what to re-check BEFORE
25
+ * retrying, because the earlier attempt may already have taken effect (a
26
+ * [`RefusalCode::UpstreamTimeout`] write); [`NextAction::render`] then
27
+ * says so. Omit it on a retry that is safe to repeat blindly.
28
+ */
29
+ target?: string | null;
30
+ /**
31
+ * READER-SIDE: the raw `kind` when this reader did not recognise it
32
+ * (`kind` is then [`NextActionKind::Unrecognised`]). Producers must not
33
+ * emit it.
34
+ */
35
+ unrecognised_kind?: string | null;
36
+ [k: string]: unknown;
37
+ }
@@ -0,0 +1,26 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ /**
9
+ * What the reader does next. A closed set: every refusal names exactly one.
10
+ */
11
+ export type NextActionKind =
12
+ | "retry_later"
13
+ | "run_command"
14
+ | "open_page"
15
+ | "sign_in"
16
+ | "pair_device"
17
+ | "set_setting"
18
+ | "wait_for_gate"
19
+ | "none_terminal"
20
+ | "report_defect"
21
+ | "fix_request"
22
+ | "resnapshot"
23
+ | "scroll_into_view"
24
+ | "wait_for_enabled"
25
+ | "broaden_selector"
26
+ | "unrecognised";
@@ -0,0 +1,44 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ /**
9
+ * Condition that runs an external command and is met iff it exits 0.
10
+ *
11
+ * The runner execs `command` directly — argv, never a shell — so `command[0]`
12
+ * is the program and the rest are its arguments; wrap it in `sh -c` yourself
13
+ * if you want shell syntax. The condition is **NOT met** on a non-zero exit,
14
+ * on a timeout (the probe is killed at `timeout_seconds`) and on a spawn
15
+ * failure (including an empty `command`); each is logged distinctly. An
16
+ * unobserved or failed probe never reads as met.
17
+ *
18
+ * The probe is rate-limited to one run per `poll_seconds` per task: between
19
+ * polls the runner reuses the last result. `poll_seconds` is floored at 60
20
+ * because the scheduler re-evaluates conditions once per 60 s tick — a shorter
21
+ * interval could not be honoured. The runner's create/update API REFUSES a
22
+ * value below the floor (and an empty `command`, and a `timeout_seconds` of
23
+ * 0 or above 3600) rather than silently rewriting it; a stored row that
24
+ * predates that validation is clamped up to 60 at evaluation time.
25
+ */
26
+ export interface ProbeCondition {
27
+ /**
28
+ * Program and arguments to exec (no shell). Must be non-empty.
29
+ */
30
+ command: string[];
31
+ /**
32
+ * Whether this condition is active.
33
+ */
34
+ enabled: boolean;
35
+ /**
36
+ * Minimum seconds between two probe runs for the same task. Minimum 60.
37
+ */
38
+ pollSeconds: number;
39
+ /**
40
+ * Seconds a single probe run may take before it is killed and counted as
41
+ * NOT met. 1..=3600.
42
+ */
43
+ timeoutSeconds: number;
44
+ }
@@ -0,0 +1,63 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ import type { GlossaryTerm } from "./GlossaryTerm";
9
+ import type { NextAction } from "./NextAction";
10
+ import type { NextActionKind } from "./NextActionKind";
11
+ import type { RefusalCode } from "./RefusalCode";
12
+ import type { RefusalSource } from "./RefusalSource";
13
+
14
+ /**
15
+ * One operator-facing refusal: what went wrong, and what to do next.
16
+ *
17
+ * Construct with [`Refusal::new`] and the `with_*` builders. `next_action` is
18
+ * deliberately not optional — see the module docs. Decoding is lenient toward
19
+ * newer producers (module docs, "Forward compatibility").
20
+ */
21
+ export interface Refusal {
22
+ code: RefusalCode;
23
+ /**
24
+ * Raw diagnostic text (the underlying error, the unrecognised reason).
25
+ * Shown beside the rendered sentence, never inside it.
26
+ */
27
+ detail?: string | null;
28
+ /**
29
+ * Narrows `code` to the specific case (the setting that was empty, the
30
+ * service whose address was missing). Rendered after the headline.
31
+ */
32
+ discriminator?: string | null;
33
+ /**
34
+ * Glossary terms a reader may need to act on this refusal. Typed, so a
35
+ * producer cannot cite a term the glossary does not define. Always
36
+ * present on the wire (an empty list, never null).
37
+ */
38
+ glossary_terms: GlossaryTerm[];
39
+ next_action: NextAction;
40
+ /**
41
+ * When the refusal was observed (ISO 8601).
42
+ */
43
+ observed_at: string;
44
+ source: RefusalSource;
45
+ /**
46
+ * READER-SIDE: the raw `code` when this reader did not recognise it
47
+ * (`code` is then [`RefusalCode::Unknown`]). Absent when the producer
48
+ * itself said `unknown`. Producers must not emit it.
49
+ */
50
+ unrecognised_code?: string | null;
51
+ /**
52
+ * READER-SIDE: glossary ids this reader's glossary does not define,
53
+ * removed from `glossary_terms`. Producers must not emit it.
54
+ */
55
+ unrecognised_glossary_terms?: string[];
56
+ /**
57
+ * READER-SIDE: the raw `source` when this reader did not recognise it
58
+ * (`source` is then [`RefusalSource::Unrecognised`]). Producers must not
59
+ * emit it.
60
+ */
61
+ unrecognised_source?: string | null;
62
+ [k: string]: unknown;
63
+ }
@@ -0,0 +1,33 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ /**
9
+ * What went wrong, as a stable machine-readable code.
10
+ *
11
+ * Extend by adding a variant (and its row in [`RefusalCode::headline`] and
12
+ * [`RefusalCode::from_wire`]); the tables are exhaustive `match`es, so a new
13
+ * code without a sentence is a compile error.
14
+ */
15
+ export type RefusalCode =
16
+ | "workspace_root_unresolved"
17
+ | "sibling_checkout_absent"
18
+ | "endpoint_unresolved"
19
+ | "glossary_term_unknown"
20
+ | "authentication_required"
21
+ | "credential_rejected"
22
+ | "permission_denied"
23
+ | "not_found"
24
+ | "conflict"
25
+ | "invalid_request"
26
+ | "rate_limited"
27
+ | "quota_exceeded"
28
+ | "upstream_unavailable"
29
+ | "upstream_timeout"
30
+ | "device_not_connected"
31
+ | "service_unavailable"
32
+ | "internal_error"
33
+ | "unknown";
@@ -0,0 +1,12 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated.
4
+ * DO NOT MODIFY IT BY HAND. Regenerate with
5
+ * `qontinui-runner/src-tauri/scripts/generate_types.sh`.
6
+ */
7
+
8
+ /**
9
+ * The component that produced a [`Refusal`].
10
+ */
11
+ export type RefusalSource =
12
+ "runner" | "coord" | "web_backend" | "web_frontend" | "unrecognised";
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { IdleCondition } from "./IdleCondition";
9
+ import type { ProbeCondition } from "./ProbeCondition";
9
10
  import type { RepositoryInactiveCondition } from "./RepositoryInactiveCondition";
10
11
  import type { RepositoryWatch } from "./RepositoryWatch";
11
12
 
@@ -17,6 +18,10 @@ export interface ScheduleConditions {
17
18
  * Require the runner to be idle.
18
19
  */
19
20
  requireIdle?: IdleCondition | null;
21
+ /**
22
+ * Require an external probe command to exit 0 (see [`ProbeCondition`]).
23
+ */
24
+ requireProbe?: ProbeCondition | null;
20
25
  /**
21
26
  * Require repository file inactivity across one or more paths.
22
27
  */
@@ -10,6 +10,7 @@ import type { ConditionScheduleConfig } from "./ConditionScheduleConfig";
10
10
  import type { ConditionStatus } from "./ConditionStatus";
11
11
  import type { IdleCondition } from "./IdleCondition";
12
12
  import type { McpConnectionRef } from "./McpConnectionRef";
13
+ import type { ProbeCondition } from "./ProbeCondition";
13
14
  import type { RepositoryInactiveCondition } from "./RepositoryInactiveCondition";
14
15
  import type { RepositoryWatch } from "./RepositoryWatch";
15
16
  import type { ScheduleConditions } from "./ScheduleConditions";
@@ -9,6 +9,7 @@ import type { CatchUpPolicy } from "./CatchUpPolicy";
9
9
  import type { ConditionScheduleConfig } from "./ConditionScheduleConfig";
10
10
  import type { IdleCondition } from "./IdleCondition";
11
11
  import type { McpConnectionRef } from "./McpConnectionRef";
12
+ import type { ProbeCondition } from "./ProbeCondition";
12
13
  import type { RepositoryInactiveCondition } from "./RepositoryInactiveCondition";
13
14
  import type { RepositoryWatch } from "./RepositoryWatch";
14
15
  import type { ScheduleConditions } from "./ScheduleConditions";
@@ -188,6 +188,7 @@ export type { FindingUpdate } from './FindingUpdate';
188
188
  export type { FindingUserInput } from './FindingUserInput';
189
189
  export type { FindingsSummary } from './FindingsSummary';
190
190
  export type { FlowEvent } from './FlowEvent';
191
+ export type { FrontierEntry } from './FrontierEntry';
191
192
  export type { FullRunnerStep } from './FullRunnerStep';
192
193
  export type { FunctionalSpec } from './FunctionalSpec';
193
194
  export type { GUIElementChunk } from './GUIElementChunk';
@@ -197,6 +198,7 @@ export type { GitCommitLite } from './GitCommitLite';
197
198
  export type { GitLite } from './GitLite';
198
199
  export type { GitOpListResponse } from './GitOpListResponse';
199
200
  export type { GitOpRecord } from './GitOpRecord';
201
+ export type { GlossaryTerm } from './GlossaryTerm';
200
202
  export type { HealthCheckUrl } from './HealthCheckUrl';
201
203
  export type { HealthLevel } from './HealthLevel';
202
204
  export type { HealthLite } from './HealthLite';
@@ -240,6 +242,10 @@ export type { JobItem } from './JobItem';
240
242
  export type { JobListResponse } from './JobListResponse';
241
243
  export type { JobStatus } from './JobStatus';
242
244
  export type { JobSummary } from './JobSummary';
245
+ export type { JourneyEdgeObservation } from './JourneyEdgeObservation';
246
+ export type { JourneyLedgerHealth } from './JourneyLedgerHealth';
247
+ export type { JourneyNode } from './JourneyNode';
248
+ export type { JourneyTrigger } from './JourneyTrigger';
243
249
  export type { LLMCostSummary } from './LLMCostSummary';
244
250
  export type { LLMMetrics } from './LLMMetrics';
245
251
  export type { LegacyAssertion } from './LegacyAssertion';
@@ -287,6 +293,8 @@ export type { MultiLoopStatus } from './MultiLoopStatus';
287
293
  export type { NativeAccessibilityStep } from './NativeAccessibilityStep';
288
294
  export type { NavigationResult } from './NavigationResult';
289
295
  export type { NewConstraintProposal } from './NewConstraintProposal';
296
+ export type { NextAction } from './NextAction';
297
+ export type { NextActionKind } from './NextActionKind';
290
298
  export type { NextTaskInfo } from './NextTaskInfo';
291
299
  export type { NodeMetadata } from './NodeMetadata';
292
300
  export type { NodeStatus } from './NodeStatus';
@@ -318,6 +326,7 @@ export type { PolicyConjunct } from './PolicyConjunct';
318
326
  export type { PolicyEvaluation } from './PolicyEvaluation';
319
327
  export type { PolicyStatus } from './PolicyStatus';
320
328
  export type { PollingConfig } from './PollingConfig';
329
+ export type { ProbeCondition } from './ProbeCondition';
321
330
  export type { ProcessConfig } from './ProcessConfig';
322
331
  export type { ProcessState } from './ProcessState';
323
332
  export type { ProcessStatus } from './ProcessStatus';
@@ -336,6 +345,9 @@ export type { ReadConfigResponse } from './ReadConfigResponse';
336
345
  export type { RecommendedState } from './RecommendedState';
337
346
  export type { RecordGitOpRequest } from './RecordGitOpRequest';
338
347
  export type { ReflectionFixOutput } from './ReflectionFixOutput';
348
+ export type { Refusal } from './Refusal';
349
+ export type { RefusalCode } from './RefusalCode';
350
+ export type { RefusalSource } from './RefusalSource';
339
351
  export type { Region } from './Region';
340
352
  export type { RegisterAppRequest } from './RegisterAppRequest';
341
353
  export type { Relationship } from './Relationship';
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ GLOSSARY,
5
+ GLOSSARY_CONTENT_SHA256,
6
+ GLOSSARY_TERMS,
7
+ GLOSSARY_VERSION,
8
+ glossaryEntry,
9
+ isGlossaryTerm,
10
+ lookupGlossaryTerm,
11
+ } from "../index";
12
+
13
+ describe("glossary table", () => {
14
+ it("lists every keyed entry exactly once, in order", () => {
15
+ expect([...GLOSSARY_TERMS]).toEqual(Object.keys(GLOSSARY));
16
+ expect(new Set(GLOSSARY_TERMS).size).toBe(GLOSSARY_TERMS.length);
17
+ expect(GLOSSARY_TERMS.length).toBeGreaterThanOrEqual(20);
18
+ });
19
+
20
+ it("keys each entry by its own id, with no dangling see_also", () => {
21
+ for (const id of GLOSSARY_TERMS) {
22
+ const e = GLOSSARY[id];
23
+ expect(e.id).toBe(id);
24
+ expect(e.term.trim()).not.toBe("");
25
+ expect([...e.short].length).toBeLessThanOrEqual(160);
26
+ expect([...e.long].length).toBeLessThanOrEqual(1200);
27
+ for (const s of e.see_also) expect(isGlossaryTerm(s)).toBe(true);
28
+ expect(e.since).toBeGreaterThanOrEqual(1);
29
+ expect(e.since).toBeLessThanOrEqual(GLOSSARY_VERSION);
30
+ }
31
+ });
32
+
33
+ it("carries its version and content digest", () => {
34
+ expect(GLOSSARY_VERSION).toBeGreaterThanOrEqual(1);
35
+ expect(GLOSSARY_CONTENT_SHA256).toMatch(/^[0-9a-f]{64}$/);
36
+ });
37
+
38
+ it("looks up by id and refuses what it does not define", () => {
39
+ expect(glossaryEntry("gate").id).toBe("gate");
40
+ expect(lookupGlossaryTerm("work_unit")?.term).toBe(GLOSSARY.work_unit.term);
41
+ expect(lookupGlossaryTerm("no_such_term")).toBeNull();
42
+ // Own keys only: an inherited property is not a term.
43
+ expect(isGlossaryTerm("toString")).toBe(false);
44
+ expect(isGlossaryTerm("__proto__")).toBe(false);
45
+ });
46
+ });