dsh-diagnostic-tutor 0.1.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 +21 -0
- package/README.md +658 -0
- package/cordis.patch.yml +23 -0
- package/lib/api.js +413 -0
- package/lib/api.js.map +1 -0
- package/lib/client.js +2029 -0
- package/lib/client.js.map +1 -0
- package/lib/contract.js +14 -0
- package/lib/contract.js.map +1 -0
- package/lib/diagnosis.js +224 -0
- package/lib/diagnosis.js.map +1 -0
- package/lib/handoff.js +194 -0
- package/lib/handoff.js.map +1 -0
- package/lib/index.js +186 -0
- package/lib/index.js.map +1 -0
- package/lib/lesson.js +285 -0
- package/lib/lesson.js.map +1 -0
- package/lib/prompt.js +96 -0
- package/lib/prompt.js.map +1 -0
- package/lib/state.js +500 -0
- package/lib/state.js.map +1 -0
- package/lib/tools.js +994 -0
- package/lib/tools.js.map +1 -0
- package/lib/trust-fence.js +101 -0
- package/lib/trust-fence.js.map +1 -0
- package/lib/types/api.d.ts +62 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/contract.d.ts +147 -0
- package/lib/types/contract.d.ts.map +1 -0
- package/lib/types/diagnosis.d.ts +116 -0
- package/lib/types/diagnosis.d.ts.map +1 -0
- package/lib/types/handoff.d.ts +141 -0
- package/lib/types/handoff.d.ts.map +1 -0
- package/lib/types/index.d.ts +71 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/lesson.d.ts +295 -0
- package/lib/types/lesson.d.ts.map +1 -0
- package/lib/types/prompt.d.ts +85 -0
- package/lib/types/prompt.d.ts.map +1 -0
- package/lib/types/state.d.ts +627 -0
- package/lib/types/state.d.ts.map +1 -0
- package/lib/types/tools.d.ts +38 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/trust-fence.d.ts +53 -0
- package/lib/types/trust-fence.d.ts.map +1 -0
- package/lib/types/udt.d.ts +95 -0
- package/lib/types/udt.d.ts.map +1 -0
- package/lib/types/vocabulary.d.ts +162 -0
- package/lib/types/vocabulary.d.ts.map +1 -0
- package/lib/udt.js +141 -0
- package/lib/udt.js.map +1 -0
- package/lib/vocabulary.js +182 -0
- package/lib/vocabulary.js.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser trust fence for this plugin's HTTP surface.
|
|
3
|
+
*
|
|
4
|
+
* A bare `ctx.webServer.register()` route inherits **no authentication** — the
|
|
5
|
+
* framework does not wrap third-party routes, and its own gateway fence is
|
|
6
|
+
* package-internal. Every published plugin therefore re-implements the same
|
|
7
|
+
* behaviour, and so does this one.
|
|
8
|
+
*
|
|
9
|
+
* What it defends against: a page on another origin (or a DNS-rebound hostname)
|
|
10
|
+
* reaching these routes from the user's browser. The rule is that a request
|
|
11
|
+
* must arrive at a **loopback** host, from a **loopback** origin, and must not
|
|
12
|
+
* be marked cross-site.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately a *behavioural* replica of
|
|
15
|
+
* `@deepseek-ai/dsh-client-connection`'s `api-request-trust.ts` /
|
|
16
|
+
* `loopback-hostname.ts`, not an import: those are not exported for third
|
|
17
|
+
* parties, and importing across a harness cohort is exactly what this project's
|
|
18
|
+
* cross-version discipline forbids.
|
|
19
|
+
*
|
|
20
|
+
* Anything that fails the fence gets 403 and no body, so a rejected caller
|
|
21
|
+
* learns nothing about what is behind it.
|
|
22
|
+
*/
|
|
23
|
+
import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';
|
|
24
|
+
/** The only part of a request the fence inspects. */
|
|
25
|
+
export interface ApiTrustRequest {
|
|
26
|
+
headers: IncomingHttpHeaders;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether a hostname is this machine.
|
|
30
|
+
*
|
|
31
|
+
* `localhost`, IPv6 loopback, and any `127.0.0.0/8` address. Everything else —
|
|
32
|
+
* including a public name that resolves to 127.0.0.1 through DNS rebinding — is
|
|
33
|
+
* rejected, because the *name* is what an attacker controls.
|
|
34
|
+
*
|
|
35
|
+
* @param hostname - the hostname from a Host or Origin header.
|
|
36
|
+
* @returns whether it denotes the local machine.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a request may reach the plugin API.
|
|
41
|
+
*
|
|
42
|
+
* @param request - the incoming request (only `headers` is read).
|
|
43
|
+
* @returns whether the request is same-origin and local.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isTrustedApiRequest(request: ApiTrustRequest): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Wrap a handler so it only runs for trusted requests.
|
|
48
|
+
*
|
|
49
|
+
* @param handler - the route body.
|
|
50
|
+
* @returns a handler that answers 403 without a body when the fence rejects.
|
|
51
|
+
*/
|
|
52
|
+
export declare function guarded(handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
53
|
+
//# sourceMappingURL=trust-fence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-fence.d.ts","sourceRoot":"","sources":["../../src/trust-fence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAErF,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,mBAAmB,CAAA;CAC7B;AAOD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQ5D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CA+BrE;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAC3E,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAU9D"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of the Universal Diagnostic Tutor skill.
|
|
3
|
+
*
|
|
4
|
+
* This module answers one question — "is the teaching brain present in this
|
|
5
|
+
* environment, and does it look like the one we mirror?" — and nothing else.
|
|
6
|
+
*
|
|
7
|
+
* How detection works, and what it deliberately avoids:
|
|
8
|
+
* - it asks the **skill registry** (`ctx.skills`), the platform's own
|
|
9
|
+
* discovery service, rather than looking at the filesystem. No path is
|
|
10
|
+
* hardcoded and no skill root is assumed;
|
|
11
|
+
* - it never copies, vendors or imports the skill's logic;
|
|
12
|
+
* - it never constructs a DSH runtime object; the registry arrives from `ctx`;
|
|
13
|
+
* - if anything is missing it returns an unavailable status with a reason
|
|
14
|
+
* instead of throwing, so a profile without the skill degrades rather than
|
|
15
|
+
* crashes.
|
|
16
|
+
*
|
|
17
|
+
* **Leakage rule.** `path` and `fingerprint` exist for the plugin's own
|
|
18
|
+
* diagnostics and for a future state panel. They must never reach
|
|
19
|
+
* learner-facing text: the skill's own protocol forbids naming its files,
|
|
20
|
+
* versions or repository in a tutoring reply, and this runtime will not be the
|
|
21
|
+
* thing that leaks them. Detection results are logged at `debug` and are
|
|
22
|
+
* deliberately absent from every tool's output schema.
|
|
23
|
+
*/
|
|
24
|
+
import type { SkillRegistry } from '@deepseek-ai/dsh-skill';
|
|
25
|
+
/** The skill's canonical name, as declared in its frontmatter. */
|
|
26
|
+
export declare const UDT_SKILL_NAME = "universal-diagnostic-tutor";
|
|
27
|
+
/** How much we were able to verify about the detected skill. */
|
|
28
|
+
export declare const UDT_COMPATIBILITIES: readonly ["compatible", "unknown", "unavailable"];
|
|
29
|
+
export type UdtCompatibility = (typeof UDT_COMPATIBILITIES)[number];
|
|
30
|
+
export interface UdtSkillStatus {
|
|
31
|
+
/** Whether the skill was found in the catalog. */
|
|
32
|
+
readonly available: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the catalog could be read well enough to conclude anything.
|
|
35
|
+
*
|
|
36
|
+
* `false` means the skill was not found **in a catalog that demonstrably has
|
|
37
|
+
* entries**, so its absence is a real finding. `true` means entries were
|
|
38
|
+
* visible and the search was meaningful.
|
|
39
|
+
*
|
|
40
|
+
* This distinction exists because the registry's `list()` reads **the global
|
|
41
|
+
* layer alone** unless given a viewing `scope`, and the standard web profile
|
|
42
|
+
* mounts the filesystem skill provider inside a nested agent layer. From a
|
|
43
|
+
* plugin at the profile root the catalog is therefore *empty* even when the
|
|
44
|
+
* skill is installed and the tutor is using it — a miss that says nothing.
|
|
45
|
+
* Treating that as "not installed" produced a confident, wrong answer.
|
|
46
|
+
*/
|
|
47
|
+
readonly catalogVisible: boolean;
|
|
48
|
+
/** The name we looked for. */
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly compatibility: UdtCompatibility;
|
|
51
|
+
/** Discovery source label reported by the registry (provider-specific). */
|
|
52
|
+
readonly source?: string;
|
|
53
|
+
/** Owning provider's name. */
|
|
54
|
+
readonly provider?: string;
|
|
55
|
+
/** Directory the skill was loaded from. INTERNAL — never learner-facing. */
|
|
56
|
+
readonly path?: string;
|
|
57
|
+
/** Short content digest; a version hint that needs no version field. INTERNAL. */
|
|
58
|
+
readonly fingerprint?: string;
|
|
59
|
+
/** Why the status is what it is. Safe to log. */
|
|
60
|
+
readonly reason?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Vocabulary that must appear in the skill body for us to call it compatible.
|
|
64
|
+
*
|
|
65
|
+
* The skill cannot declare a version — its maintenance contract permits only
|
|
66
|
+
* `name` and `description` in frontmatter — so compatibility is probed by
|
|
67
|
+
* *capability* instead: the terms this runtime mirrors must actually be there.
|
|
68
|
+
* That is more robust than a version string and needs no upstream change.
|
|
69
|
+
*
|
|
70
|
+
* These are deliberately few and structural: the Core Loop's `Diagnose` step
|
|
71
|
+
* and two ends of the seven-term status vocabulary.
|
|
72
|
+
*/
|
|
73
|
+
export declare const CAPABILITY_ANCHORS: readonly ["Diagnose", "unconfirmed", "blocked"];
|
|
74
|
+
/** Stable short digest of a skill body, used as a version hint. */
|
|
75
|
+
export declare function fingerprintContent(content: string): string;
|
|
76
|
+
/**
|
|
77
|
+
* Detect the teaching brain.
|
|
78
|
+
*
|
|
79
|
+
* @param skills - the skill registry taken from `ctx`, or `undefined` when the
|
|
80
|
+
* profile does not mount one.
|
|
81
|
+
* @returns the detection status; never throws.
|
|
82
|
+
*/
|
|
83
|
+
export declare function detectUdtSkill(skills: SkillRegistry | undefined): Promise<UdtSkillStatus>;
|
|
84
|
+
/**
|
|
85
|
+
* Render a detection status as one log line.
|
|
86
|
+
*
|
|
87
|
+
* Kept here so the one place that knows about paths is also the one place that
|
|
88
|
+
* formats them, and so that callers cannot accidentally interpolate this into
|
|
89
|
+
* learner-facing text.
|
|
90
|
+
*
|
|
91
|
+
* @param status - the detection result.
|
|
92
|
+
* @returns a single line suitable for the harness log.
|
|
93
|
+
*/
|
|
94
|
+
export declare function describeUdtStatus(status: UdtSkillStatus): string;
|
|
95
|
+
//# sourceMappingURL=udt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"udt.d.ts","sourceRoot":"","sources":["../../src/udt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAE3D,kEAAkE;AAClE,eAAO,MAAM,cAAc,+BAA+B,CAAA;AAE1D,gEAAgE;AAChE,eAAO,MAAM,mBAAmB,mDAAoD,CAAA;AACpF,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAA;IAChC,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAA;IACxC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,iDAAiD;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,iDAAkD,CAAA;AAEjF,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1D;AAMD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAgF/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAKhE"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed vocabularies this runtime is allowed to speak.
|
|
3
|
+
*
|
|
4
|
+
* Every term below is **mirrored verbatim** from the Universal Diagnostic
|
|
5
|
+
* Tutor skill. None is invented here, and that is a hard rule rather than a
|
|
6
|
+
* preference: the skill's own mastery protocol says "The only concept-level
|
|
7
|
+
* status terms are these seven … never invent a second vocabulary."
|
|
8
|
+
*
|
|
9
|
+
* If a term needs to change, it changes in the skill first and is mirrored
|
|
10
|
+
* here second. Divergence would make the runtime and the teaching brain
|
|
11
|
+
* disagree about the same learner, which is the one failure this project
|
|
12
|
+
* cannot survive.
|
|
13
|
+
*
|
|
14
|
+
* Sources (skill `references/`):
|
|
15
|
+
* - seven node states -> `mastery_and_decision.md`
|
|
16
|
+
* - six readiness gates -> `mastery_and_decision.md`
|
|
17
|
+
* - teaching modes -> `teaching_modes.md`
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
/**
|
|
21
|
+
* The seven status terms, exactly as the skill defines them:
|
|
22
|
+
*
|
|
23
|
+
* | term | meaning |
|
|
24
|
+
* | ----------- | ---------------------------------------------------- |
|
|
25
|
+
* | unconfirmed | no evidence yet, even if related content was discussed |
|
|
26
|
+
* | explained | the tutor explained it; mastery not proven |
|
|
27
|
+
* | practiced | the learner attempted at least one task |
|
|
28
|
+
* | checked | a check or near-transfer question was asked |
|
|
29
|
+
* | weak | partial understanding or unstable use |
|
|
30
|
+
* | blocked | cannot proceed; a prerequisite is missing or misread |
|
|
31
|
+
* | confirmed | sound reasoning plus independent use or transfer |
|
|
32
|
+
*/
|
|
33
|
+
export declare const NODE_STATES: readonly ["unconfirmed", "explained", "practiced", "checked", "weak", "blocked", "confirmed"];
|
|
34
|
+
export declare const NodeStateSchema: z.ZodEnum<{
|
|
35
|
+
unconfirmed: "unconfirmed";
|
|
36
|
+
explained: "explained";
|
|
37
|
+
practiced: "practiced";
|
|
38
|
+
checked: "checked";
|
|
39
|
+
weak: "weak";
|
|
40
|
+
blocked: "blocked";
|
|
41
|
+
confirmed: "confirmed";
|
|
42
|
+
}>;
|
|
43
|
+
export type NodeState = z.infer<typeof NodeStateSchema>;
|
|
44
|
+
/** The state a node is born in. Nothing is ever born confirmed. */
|
|
45
|
+
export declare const INITIAL_NODE_STATE: NodeState;
|
|
46
|
+
export declare const READINESS_OUTCOMES: readonly ["advance", "advance-with-caution", "review-first", "step-down", "diagnose-again", "more-practice"];
|
|
47
|
+
export declare const ReadinessSchema: z.ZodEnum<{
|
|
48
|
+
advance: "advance";
|
|
49
|
+
"advance-with-caution": "advance-with-caution";
|
|
50
|
+
"review-first": "review-first";
|
|
51
|
+
"step-down": "step-down";
|
|
52
|
+
"diagnose-again": "diagnose-again";
|
|
53
|
+
"more-practice": "more-practice";
|
|
54
|
+
}>;
|
|
55
|
+
export type Readiness = z.infer<typeof ReadinessSchema>;
|
|
56
|
+
/**
|
|
57
|
+
* What each readiness outcome means for *navigation*.
|
|
58
|
+
*
|
|
59
|
+
* The decision vocabulary is the skill's six readiness outcomes, reused as-is.
|
|
60
|
+
* Inventing a second set of words for "what happens next" would be exactly the
|
|
61
|
+
* duplication this project exists to avoid — and the six already say it: an
|
|
62
|
+
* outcome is a judgement about the current concept, and moving or staying is
|
|
63
|
+
* what that judgement implies.
|
|
64
|
+
*
|
|
65
|
+
* The runtime only needs to know one structural thing about each outcome:
|
|
66
|
+
* whether it names a different node to go to.
|
|
67
|
+
*
|
|
68
|
+
* | outcome | target | means |
|
|
69
|
+
* | ---------------------- | ------------- | -------------------------------------- |
|
|
70
|
+
* | `advance` | required | move to that node |
|
|
71
|
+
* | `advance-with-caution` | required | move, with an early check there |
|
|
72
|
+
* | `step-down` | required | the blocker; usually a prerequisite |
|
|
73
|
+
* | `review-first` | optional | go back to that node, or review here |
|
|
74
|
+
* | `more-practice` | forbidden | stay here and practise |
|
|
75
|
+
* | `diagnose-again` | forbidden | stay here and ask a sharper question |
|
|
76
|
+
*/
|
|
77
|
+
export type TargetRequirement = 'required' | 'optional' | 'forbidden';
|
|
78
|
+
/**
|
|
79
|
+
* Whether an outcome must, may, or must not name a target node.
|
|
80
|
+
*
|
|
81
|
+
* @param action - one of the skill's readiness outcomes.
|
|
82
|
+
* @returns the structural requirement.
|
|
83
|
+
*/
|
|
84
|
+
export declare function targetRequirement(action: Readiness): TargetRequirement;
|
|
85
|
+
/**
|
|
86
|
+
* Whether an outcome ends the current focus.
|
|
87
|
+
*
|
|
88
|
+
* Only a genuine move does. Staying keeps the focus active, because the learner
|
|
89
|
+
* is still working the same node — ending it there would make the panel claim
|
|
90
|
+
* they had moved on.
|
|
91
|
+
*
|
|
92
|
+
* @param action - one of the skill's readiness outcomes.
|
|
93
|
+
* @param hasTarget - whether a target node was named.
|
|
94
|
+
* @returns whether the focus should end.
|
|
95
|
+
*/
|
|
96
|
+
export declare function endsFocus(action: Readiness, hasTarget: boolean): boolean;
|
|
97
|
+
export declare const TEACHING_MODES: readonly ["auto", "zero-base", "standard", "advanced"];
|
|
98
|
+
export declare const TeachingModeSchema: z.ZodEnum<{
|
|
99
|
+
auto: "auto";
|
|
100
|
+
"zero-base": "zero-base";
|
|
101
|
+
standard: "standard";
|
|
102
|
+
advanced: "advanced";
|
|
103
|
+
}>;
|
|
104
|
+
export type TeachingMode = z.infer<typeof TeachingModeSchema>;
|
|
105
|
+
/**
|
|
106
|
+
* How a node relates to its parent.
|
|
107
|
+
*
|
|
108
|
+
* These describe *diagnosis*, not a syllabus: `prerequisite` means "this was
|
|
109
|
+
* found to block that", not "week 2 comes after week 1". There is deliberately
|
|
110
|
+
* no `next-in-course` relation, because nothing in this runtime knows a
|
|
111
|
+
* teaching order — the skill decides that per session.
|
|
112
|
+
*/
|
|
113
|
+
export declare const NODE_RELATIONS: readonly ["goal", "part-of", "prerequisite", "related"];
|
|
114
|
+
export declare const NodeRelationSchema: z.ZodEnum<{
|
|
115
|
+
goal: "goal";
|
|
116
|
+
"part-of": "part-of";
|
|
117
|
+
prerequisite: "prerequisite";
|
|
118
|
+
related: "related";
|
|
119
|
+
}>;
|
|
120
|
+
export type NodeRelation = z.infer<typeof NodeRelationSchema>;
|
|
121
|
+
/**
|
|
122
|
+
* The kinds of evidence the runtime records.
|
|
123
|
+
*
|
|
124
|
+
* This list is intentionally about *observations*, not judgements: the runtime
|
|
125
|
+
* stores what happened, and never whether it was good.
|
|
126
|
+
*/
|
|
127
|
+
export declare const EVIDENCE_KINDS: readonly ["goal-stated", "diagnosis", "explanation", "practice", "check", "transfer"];
|
|
128
|
+
export declare const EvidenceKindSchema: z.ZodEnum<{
|
|
129
|
+
"goal-stated": "goal-stated";
|
|
130
|
+
diagnosis: "diagnosis";
|
|
131
|
+
explanation: "explanation";
|
|
132
|
+
practice: "practice";
|
|
133
|
+
check: "check";
|
|
134
|
+
transfer: "transfer";
|
|
135
|
+
}>;
|
|
136
|
+
export type EvidenceKind = z.infer<typeof EvidenceKindSchema>;
|
|
137
|
+
/**
|
|
138
|
+
* Evidence kinds strong enough to support `confirmed`.
|
|
139
|
+
*
|
|
140
|
+
* The skill is explicit that the weaker signals never confirm: "a correct
|
|
141
|
+
* answer is not proof of reasoning … explanation alone and one lucky answer
|
|
142
|
+
* never confirm readiness." So `explanation`, `practice` and the purely
|
|
143
|
+
* observational kinds cannot confirm a node, and the runtime refuses to let
|
|
144
|
+
* them — see `diagnosis.ts`.
|
|
145
|
+
*
|
|
146
|
+
* This is a structural floor, not a pedagogical verdict: clearing it makes
|
|
147
|
+
* `confirmed` *permissible*, and the skill still decides whether it is right.
|
|
148
|
+
*/
|
|
149
|
+
export declare const CONFIRMING_EVIDENCE_KINDS: readonly ["check", "transfer"];
|
|
150
|
+
export type ConfirmingEvidenceKind = (typeof CONFIRMING_EVIDENCE_KINDS)[number];
|
|
151
|
+
/** Whether an evidence kind may support `confirmed`. */
|
|
152
|
+
export declare function isConfirmingEvidence(kind: EvidenceKind): kind is ConfirmingEvidenceKind;
|
|
153
|
+
/**
|
|
154
|
+
* Field names that would turn learning state into a grade.
|
|
155
|
+
*
|
|
156
|
+
* The skill forbids it in prose ("Never turn mastery tracking into scores") and
|
|
157
|
+
* the zod schemas already strip unknown keys, so no such field can be stored.
|
|
158
|
+
* This list exists so the prohibition is asserted by tests rather than trusted
|
|
159
|
+
* to review.
|
|
160
|
+
*/
|
|
161
|
+
export declare const FORBIDDEN_SCORE_FIELDS: readonly ["score", "grade", "points", "percent", "percentage", "progress", "stars", "rating", "level", "xp"];
|
|
162
|
+
//# sourceMappingURL=vocabulary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../../src/vocabulary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAMvB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,+FAQd,CAAA;AACV,eAAO,MAAM,eAAe;;;;;;;;EAAsB,CAAA;AAClD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAEvD,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,EAAE,SAAyB,CAAA;AAM1D,eAAO,MAAM,kBAAkB,8GAOrB,CAAA;AACV,eAAO,MAAM,eAAe;;;;;;;EAA6B,CAAA;AACzD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAEvD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,CAAA;AAWrE;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,iBAAiB,CAEtE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAExE;AAMD,eAAO,MAAM,cAAc,wDAAyD,CAAA;AACpF,eAAO,MAAM,kBAAkB;;;;;EAAyB,CAAA;AACxD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAM7D;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,yDASjB,CAAA;AACV,eAAO,MAAM,kBAAkB;;;;;EAAyB,CAAA;AACxD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAM7D;;;;;GAKG;AACH,eAAO,MAAM,cAAc,uFAajB,CAAA;AACV,eAAO,MAAM,kBAAkB;;;;;;;EAAyB,CAAA;AACxD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE7D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,gCAAiC,CAAA;AACvE,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE/E,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,IAAI,sBAAsB,CAEvF;AAMD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,8GAWzB,CAAA"}
|
package/lib/udt.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of the Universal Diagnostic Tutor skill.
|
|
3
|
+
*
|
|
4
|
+
* This module answers one question — "is the teaching brain present in this
|
|
5
|
+
* environment, and does it look like the one we mirror?" — and nothing else.
|
|
6
|
+
*
|
|
7
|
+
* How detection works, and what it deliberately avoids:
|
|
8
|
+
* - it asks the **skill registry** (`ctx.skills`), the platform's own
|
|
9
|
+
* discovery service, rather than looking at the filesystem. No path is
|
|
10
|
+
* hardcoded and no skill root is assumed;
|
|
11
|
+
* - it never copies, vendors or imports the skill's logic;
|
|
12
|
+
* - it never constructs a DSH runtime object; the registry arrives from `ctx`;
|
|
13
|
+
* - if anything is missing it returns an unavailable status with a reason
|
|
14
|
+
* instead of throwing, so a profile without the skill degrades rather than
|
|
15
|
+
* crashes.
|
|
16
|
+
*
|
|
17
|
+
* **Leakage rule.** `path` and `fingerprint` exist for the plugin's own
|
|
18
|
+
* diagnostics and for a future state panel. They must never reach
|
|
19
|
+
* learner-facing text: the skill's own protocol forbids naming its files,
|
|
20
|
+
* versions or repository in a tutoring reply, and this runtime will not be the
|
|
21
|
+
* thing that leaks them. Detection results are logged at `debug` and are
|
|
22
|
+
* deliberately absent from every tool's output schema.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
/** The skill's canonical name, as declared in its frontmatter. */
|
|
26
|
+
export const UDT_SKILL_NAME = 'universal-diagnostic-tutor';
|
|
27
|
+
/** How much we were able to verify about the detected skill. */
|
|
28
|
+
export const UDT_COMPATIBILITIES = ['compatible', 'unknown', 'unavailable'];
|
|
29
|
+
/**
|
|
30
|
+
* Vocabulary that must appear in the skill body for us to call it compatible.
|
|
31
|
+
*
|
|
32
|
+
* The skill cannot declare a version — its maintenance contract permits only
|
|
33
|
+
* `name` and `description` in frontmatter — so compatibility is probed by
|
|
34
|
+
* *capability* instead: the terms this runtime mirrors must actually be there.
|
|
35
|
+
* That is more robust than a version string and needs no upstream change.
|
|
36
|
+
*
|
|
37
|
+
* These are deliberately few and structural: the Core Loop's `Diagnose` step
|
|
38
|
+
* and two ends of the seven-term status vocabulary.
|
|
39
|
+
*/
|
|
40
|
+
export const CAPABILITY_ANCHORS = ['Diagnose', 'unconfirmed', 'blocked'];
|
|
41
|
+
/** Stable short digest of a skill body, used as a version hint. */
|
|
42
|
+
export function fingerprintContent(content) {
|
|
43
|
+
return createHash('sha256').update(content, 'utf8').digest('hex').slice(0, 12);
|
|
44
|
+
}
|
|
45
|
+
function unavailable(name, reason, catalogVisible = false) {
|
|
46
|
+
return { available: false, name, compatibility: 'unavailable', reason, catalogVisible };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Detect the teaching brain.
|
|
50
|
+
*
|
|
51
|
+
* @param skills - the skill registry taken from `ctx`, or `undefined` when the
|
|
52
|
+
* profile does not mount one.
|
|
53
|
+
* @returns the detection status; never throws.
|
|
54
|
+
*/
|
|
55
|
+
export async function detectUdtSkill(skills) {
|
|
56
|
+
if (!skills) {
|
|
57
|
+
return unavailable(UDT_SKILL_NAME, 'no skill catalog is mounted in this profile');
|
|
58
|
+
}
|
|
59
|
+
let summaries;
|
|
60
|
+
try {
|
|
61
|
+
summaries = await skills.list();
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return unavailable(UDT_SKILL_NAME, `skill discovery failed: ${error.message}`);
|
|
65
|
+
}
|
|
66
|
+
const summary = summaries.find((entry) => entry.name === UDT_SKILL_NAME);
|
|
67
|
+
if (!summary) {
|
|
68
|
+
// An empty catalog proves nothing: see `catalogVisible`. Report the miss
|
|
69
|
+
// either way, but only claim absence when entries were actually visible.
|
|
70
|
+
return summaries.length === 0
|
|
71
|
+
? unavailable(UDT_SKILL_NAME, 'no skills are visible from this profile scope; the catalog is mounted per agent ' +
|
|
72
|
+
'in some profiles, so nothing can be concluded about what is installed', false)
|
|
73
|
+
: unavailable(UDT_SKILL_NAME, `not among the ${summaries.length} skills visible in this scope`, true);
|
|
74
|
+
}
|
|
75
|
+
const base = {
|
|
76
|
+
available: true,
|
|
77
|
+
catalogVisible: true,
|
|
78
|
+
name: UDT_SKILL_NAME,
|
|
79
|
+
source: summary.source,
|
|
80
|
+
provider: summary.provider,
|
|
81
|
+
};
|
|
82
|
+
// `list()` gives a cheap location via `resourceBase`; only a directory-based
|
|
83
|
+
// provider has a path at all, and a remote provider legitimately has none.
|
|
84
|
+
const listedPath = summary.resourceBase?.kind === 'directory' ? summary.resourceBase.path : undefined;
|
|
85
|
+
let definition;
|
|
86
|
+
try {
|
|
87
|
+
definition = await skills.get(UDT_SKILL_NAME);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
...base,
|
|
92
|
+
compatibility: 'unknown',
|
|
93
|
+
reason: `listed but its body could not be loaded: ${error.message}`,
|
|
94
|
+
...(listedPath === undefined ? {} : { path: listedPath }),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (!definition) {
|
|
98
|
+
return {
|
|
99
|
+
...base,
|
|
100
|
+
compatibility: 'unknown',
|
|
101
|
+
reason: 'listed but its body could not be loaded',
|
|
102
|
+
...(listedPath === undefined ? {} : { path: listedPath }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const path = definition.path ?? listedPath;
|
|
106
|
+
const fingerprint = fingerprintContent(definition.content);
|
|
107
|
+
const missing = CAPABILITY_ANCHORS.filter((anchor) => !definition.content.includes(anchor));
|
|
108
|
+
if (missing.length > 0) {
|
|
109
|
+
return {
|
|
110
|
+
...base,
|
|
111
|
+
compatibility: 'unknown',
|
|
112
|
+
fingerprint,
|
|
113
|
+
reason: `body is missing expected vocabulary: ${missing.join(', ')}`,
|
|
114
|
+
...(path === undefined ? {} : { path }),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
...base,
|
|
119
|
+
compatibility: 'compatible',
|
|
120
|
+
fingerprint,
|
|
121
|
+
...(path === undefined ? {} : { path }),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Render a detection status as one log line.
|
|
126
|
+
*
|
|
127
|
+
* Kept here so the one place that knows about paths is also the one place that
|
|
128
|
+
* formats them, and so that callers cannot accidentally interpolate this into
|
|
129
|
+
* learner-facing text.
|
|
130
|
+
*
|
|
131
|
+
* @param status - the detection result.
|
|
132
|
+
* @returns a single line suitable for the harness log.
|
|
133
|
+
*/
|
|
134
|
+
export function describeUdtStatus(status) {
|
|
135
|
+
if (!status.available)
|
|
136
|
+
return `unavailable (${status.reason ?? 'unknown reason'})`;
|
|
137
|
+
const where = status.provider === undefined ? '' : ` via ${status.provider}`;
|
|
138
|
+
const digest = status.fingerprint === undefined ? '' : ` [${status.fingerprint}]`;
|
|
139
|
+
return `${status.compatibility}${where}${digest}`;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=udt.js.map
|
package/lib/udt.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"udt.js","sourceRoot":"","sources":["../src/udt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed vocabularies this runtime is allowed to speak.
|
|
3
|
+
*
|
|
4
|
+
* Every term below is **mirrored verbatim** from the Universal Diagnostic
|
|
5
|
+
* Tutor skill. None is invented here, and that is a hard rule rather than a
|
|
6
|
+
* preference: the skill's own mastery protocol says "The only concept-level
|
|
7
|
+
* status terms are these seven … never invent a second vocabulary."
|
|
8
|
+
*
|
|
9
|
+
* If a term needs to change, it changes in the skill first and is mirrored
|
|
10
|
+
* here second. Divergence would make the runtime and the teaching brain
|
|
11
|
+
* disagree about the same learner, which is the one failure this project
|
|
12
|
+
* cannot survive.
|
|
13
|
+
*
|
|
14
|
+
* Sources (skill `references/`):
|
|
15
|
+
* - seven node states -> `mastery_and_decision.md`
|
|
16
|
+
* - six readiness gates -> `mastery_and_decision.md`
|
|
17
|
+
* - teaching modes -> `teaching_modes.md`
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
/* -------------------------------------------------------------------------- */
|
|
21
|
+
/* Node state — the skill's seven concept-level status terms */
|
|
22
|
+
/* -------------------------------------------------------------------------- */
|
|
23
|
+
/**
|
|
24
|
+
* The seven status terms, exactly as the skill defines them:
|
|
25
|
+
*
|
|
26
|
+
* | term | meaning |
|
|
27
|
+
* | ----------- | ---------------------------------------------------- |
|
|
28
|
+
* | unconfirmed | no evidence yet, even if related content was discussed |
|
|
29
|
+
* | explained | the tutor explained it; mastery not proven |
|
|
30
|
+
* | practiced | the learner attempted at least one task |
|
|
31
|
+
* | checked | a check or near-transfer question was asked |
|
|
32
|
+
* | weak | partial understanding or unstable use |
|
|
33
|
+
* | blocked | cannot proceed; a prerequisite is missing or misread |
|
|
34
|
+
* | confirmed | sound reasoning plus independent use or transfer |
|
|
35
|
+
*/
|
|
36
|
+
export const NODE_STATES = [
|
|
37
|
+
'unconfirmed',
|
|
38
|
+
'explained',
|
|
39
|
+
'practiced',
|
|
40
|
+
'checked',
|
|
41
|
+
'weak',
|
|
42
|
+
'blocked',
|
|
43
|
+
'confirmed',
|
|
44
|
+
];
|
|
45
|
+
export const NodeStateSchema = z.enum(NODE_STATES);
|
|
46
|
+
/** The state a node is born in. Nothing is ever born confirmed. */
|
|
47
|
+
export const INITIAL_NODE_STATE = 'unconfirmed';
|
|
48
|
+
/* -------------------------------------------------------------------------- */
|
|
49
|
+
/* Readiness — the skill's six outcomes of the readiness gate */
|
|
50
|
+
/* -------------------------------------------------------------------------- */
|
|
51
|
+
export const READINESS_OUTCOMES = [
|
|
52
|
+
'advance',
|
|
53
|
+
'advance-with-caution',
|
|
54
|
+
'review-first',
|
|
55
|
+
'step-down',
|
|
56
|
+
'diagnose-again',
|
|
57
|
+
'more-practice',
|
|
58
|
+
];
|
|
59
|
+
export const ReadinessSchema = z.enum(READINESS_OUTCOMES);
|
|
60
|
+
const TARGET_REQUIREMENT = {
|
|
61
|
+
advance: 'required',
|
|
62
|
+
'advance-with-caution': 'required',
|
|
63
|
+
'step-down': 'required',
|
|
64
|
+
'review-first': 'optional',
|
|
65
|
+
'more-practice': 'forbidden',
|
|
66
|
+
'diagnose-again': 'forbidden',
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Whether an outcome must, may, or must not name a target node.
|
|
70
|
+
*
|
|
71
|
+
* @param action - one of the skill's readiness outcomes.
|
|
72
|
+
* @returns the structural requirement.
|
|
73
|
+
*/
|
|
74
|
+
export function targetRequirement(action) {
|
|
75
|
+
return TARGET_REQUIREMENT[action];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether an outcome ends the current focus.
|
|
79
|
+
*
|
|
80
|
+
* Only a genuine move does. Staying keeps the focus active, because the learner
|
|
81
|
+
* is still working the same node — ending it there would make the panel claim
|
|
82
|
+
* they had moved on.
|
|
83
|
+
*
|
|
84
|
+
* @param action - one of the skill's readiness outcomes.
|
|
85
|
+
* @param hasTarget - whether a target node was named.
|
|
86
|
+
* @returns whether the focus should end.
|
|
87
|
+
*/
|
|
88
|
+
export function endsFocus(action, hasTarget) {
|
|
89
|
+
return targetRequirement(action) === 'required' || (action === 'review-first' && hasTarget);
|
|
90
|
+
}
|
|
91
|
+
/* -------------------------------------------------------------------------- */
|
|
92
|
+
/* Teaching mode — the skill's four modes */
|
|
93
|
+
/* -------------------------------------------------------------------------- */
|
|
94
|
+
export const TEACHING_MODES = ['auto', 'zero-base', 'standard', 'advanced'];
|
|
95
|
+
export const TeachingModeSchema = z.enum(TEACHING_MODES);
|
|
96
|
+
/* -------------------------------------------------------------------------- */
|
|
97
|
+
/* Map relations — how a node attaches to the diagnosis map */
|
|
98
|
+
/* -------------------------------------------------------------------------- */
|
|
99
|
+
/**
|
|
100
|
+
* How a node relates to its parent.
|
|
101
|
+
*
|
|
102
|
+
* These describe *diagnosis*, not a syllabus: `prerequisite` means "this was
|
|
103
|
+
* found to block that", not "week 2 comes after week 1". There is deliberately
|
|
104
|
+
* no `next-in-course` relation, because nothing in this runtime knows a
|
|
105
|
+
* teaching order — the skill decides that per session.
|
|
106
|
+
*/
|
|
107
|
+
export const NODE_RELATIONS = [
|
|
108
|
+
/** The single root node of a course: the goal itself, in the learner's words. */
|
|
109
|
+
'goal',
|
|
110
|
+
/** A component the goal decomposes into, revealed by diagnosis. */
|
|
111
|
+
'part-of',
|
|
112
|
+
/** Found to block its parent; must be resolved first. */
|
|
113
|
+
'prerequisite',
|
|
114
|
+
/** Associated knowledge that is neither a component nor a blocker. */
|
|
115
|
+
'related',
|
|
116
|
+
];
|
|
117
|
+
export const NodeRelationSchema = z.enum(NODE_RELATIONS);
|
|
118
|
+
/* -------------------------------------------------------------------------- */
|
|
119
|
+
/* Evidence — what a state change is allowed to rest on */
|
|
120
|
+
/* -------------------------------------------------------------------------- */
|
|
121
|
+
/**
|
|
122
|
+
* The kinds of evidence the runtime records.
|
|
123
|
+
*
|
|
124
|
+
* This list is intentionally about *observations*, not judgements: the runtime
|
|
125
|
+
* stores what happened, and never whether it was good.
|
|
126
|
+
*/
|
|
127
|
+
export const EVIDENCE_KINDS = [
|
|
128
|
+
/** The learner stated the goal themselves. */
|
|
129
|
+
'goal-stated',
|
|
130
|
+
/** A gap, prerequisite or blocker was identified. */
|
|
131
|
+
'diagnosis',
|
|
132
|
+
/** The learner explained something in their own words. */
|
|
133
|
+
'explanation',
|
|
134
|
+
/** The learner attempted a task. */
|
|
135
|
+
'practice',
|
|
136
|
+
/** A check or near-transfer question was asked and answered. */
|
|
137
|
+
'check',
|
|
138
|
+
/** Independent use or transfer was demonstrated. */
|
|
139
|
+
'transfer',
|
|
140
|
+
];
|
|
141
|
+
export const EvidenceKindSchema = z.enum(EVIDENCE_KINDS);
|
|
142
|
+
/**
|
|
143
|
+
* Evidence kinds strong enough to support `confirmed`.
|
|
144
|
+
*
|
|
145
|
+
* The skill is explicit that the weaker signals never confirm: "a correct
|
|
146
|
+
* answer is not proof of reasoning … explanation alone and one lucky answer
|
|
147
|
+
* never confirm readiness." So `explanation`, `practice` and the purely
|
|
148
|
+
* observational kinds cannot confirm a node, and the runtime refuses to let
|
|
149
|
+
* them — see `diagnosis.ts`.
|
|
150
|
+
*
|
|
151
|
+
* This is a structural floor, not a pedagogical verdict: clearing it makes
|
|
152
|
+
* `confirmed` *permissible*, and the skill still decides whether it is right.
|
|
153
|
+
*/
|
|
154
|
+
export const CONFIRMING_EVIDENCE_KINDS = ['check', 'transfer'];
|
|
155
|
+
/** Whether an evidence kind may support `confirmed`. */
|
|
156
|
+
export function isConfirmingEvidence(kind) {
|
|
157
|
+
return CONFIRMING_EVIDENCE_KINDS.includes(kind);
|
|
158
|
+
}
|
|
159
|
+
/* -------------------------------------------------------------------------- */
|
|
160
|
+
/* Things the runtime must never produce */
|
|
161
|
+
/* -------------------------------------------------------------------------- */
|
|
162
|
+
/**
|
|
163
|
+
* Field names that would turn learning state into a grade.
|
|
164
|
+
*
|
|
165
|
+
* The skill forbids it in prose ("Never turn mastery tracking into scores") and
|
|
166
|
+
* the zod schemas already strip unknown keys, so no such field can be stored.
|
|
167
|
+
* This list exists so the prohibition is asserted by tests rather than trusted
|
|
168
|
+
* to review.
|
|
169
|
+
*/
|
|
170
|
+
export const FORBIDDEN_SCORE_FIELDS = [
|
|
171
|
+
'score',
|
|
172
|
+
'grade',
|
|
173
|
+
'points',
|
|
174
|
+
'percent',
|
|
175
|
+
'percentage',
|
|
176
|
+
'progress',
|
|
177
|
+
'stars',
|
|
178
|
+
'rating',
|
|
179
|
+
'level',
|
|
180
|
+
'xp',
|
|
181
|
+
];
|
|
182
|
+
//# sourceMappingURL=vocabulary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vocabulary.js","sourceRoot":"","sources":["../src/vocabulary.ts"],"names":[],"mappings":"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"}
|