@plasius/learning 0.2.20 → 0.2.21

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/README.md CHANGED
@@ -54,6 +54,37 @@ const result = calculateAssessment(rubric, [
54
54
  console.log(result.score, result.completed);
55
55
  ```
56
56
 
57
+ ## Bind contextual and spoken help
58
+
59
+ Contextual-help identifiers are immutable module/manifest references rather
60
+ than free-form learner text. Voice consent is separate from general AI consent,
61
+ and version one permits private-edge transcription only.
62
+
63
+ ```ts
64
+ import {
65
+ CONTEXTUAL_HELP_CONTRACT_VERSION_V1,
66
+ assertValidContextualHelpIdentifier,
67
+ type ContextualHelpIdentifierV1,
68
+ } from "@plasius/learning";
69
+
70
+ const help: ContextualHelpIdentifierV1 = {
71
+ schemaVersion: "1",
72
+ contractVersion: CONTEXTUAL_HELP_CONTRACT_VERSION_V1,
73
+ kind: "command",
74
+ moduleId: "junior-coder.road-hopper-rally",
75
+ moduleVersion: "1.1.0",
76
+ manifestVersion: "1.0.0",
77
+ helpId: "command.draw-lane",
78
+ };
79
+
80
+ assertValidContextualHelpIdentifier(help);
81
+ ```
82
+
83
+ The package also exports `GuardianVoiceConsentV1`,
84
+ `VoiceHelpAvailabilityV1`, bounded voice question metadata/results and
85
+ `CanonicalSpokenHelpDescriptorV1`. It does not record audio, persist consent,
86
+ call a transcription or synthesis provider, or evaluate runtime rollout flags.
87
+
57
88
  ## Validate a Guardian-approved static project
58
89
 
59
90
  The publishing contracts cover only immutable evidence and safe render models.
package/dist/index.cjs CHANGED
@@ -22,6 +22,10 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ADVENTURE_MISSION_PLANNER_MISSION_ONE_AUTHORING_V1: () => ADVENTURE_MISSION_PLANNER_MISSION_ONE_AUTHORING_V1,
24
24
  BEACON_BOT_MISSION_ONE_AUTHORING_V1: () => BEACON_BOT_MISSION_ONE_AUTHORING_V1,
25
+ CONTEXTUAL_HELP_CONTRACT_VERSION_V1: () => CONTEXTUAL_HELP_CONTRACT_VERSION_V1,
26
+ CONTEXTUAL_HELP_KINDS_V1: () => CONTEXTUAL_HELP_KINDS_V1,
27
+ CONTEXTUAL_VOICE_INTENTS_V1: () => CONTEXTUAL_VOICE_INTENTS_V1,
28
+ CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1: () => CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1,
25
29
  CREATURE_CARE_DASHBOARD_MISSION_ONE_AUTHORING_V1: () => CREATURE_CARE_DASHBOARD_MISSION_ONE_AUTHORING_V1,
26
30
  DANCE_ROVER_MISSION_ONE_AUTHORING_V1: () => DANCE_ROVER_MISSION_ONE_AUTHORING_V1,
27
31
  JUNIOR_CODER_MISSION_STAGE_ORDER_V1: () => JUNIOR_CODER_MISSION_STAGE_ORDER_V1,
@@ -51,11 +55,17 @@ __export(index_exports, {
51
55
  VIBE_BUG_DETECTIVE_MISSION_ONE_AUTHORING_V1: () => VIBE_BUG_DETECTIVE_MISSION_ONE_AUTHORING_V1,
52
56
  VIBE_GAME_REMIX_LAB_MISSION_ONE_AUTHORING_V1: () => VIBE_GAME_REMIX_LAB_MISSION_ONE_AUTHORING_V1,
53
57
  VIBE_IDEA_STUDIO_MISSION_ONE_AUTHORING_V1: () => VIBE_IDEA_STUDIO_MISSION_ONE_AUTHORING_V1,
58
+ assertValidCanonicalSpokenHelpDescriptor: () => assertValidCanonicalSpokenHelpDescriptor,
59
+ assertValidContextualHelpIdentifier: () => assertValidContextualHelpIdentifier,
60
+ assertValidContextualVoiceQuestionMetadata: () => assertValidContextualVoiceQuestionMetadata,
61
+ assertValidContextualVoiceQuestionResult: () => assertValidContextualVoiceQuestionResult,
62
+ assertValidGuardianVoiceConsent: () => assertValidGuardianVoiceConsent,
54
63
  assertValidLearningPath: () => assertValidLearningPath,
55
64
  assertValidMissionAuthoringBundle: () => assertValidMissionAuthoringBundle,
56
65
  assertValidStaticProjectGuardianApproval: () => assertValidStaticProjectGuardianApproval,
57
66
  assertValidStaticProjectPublication: () => assertValidStaticProjectPublication,
58
67
  assertValidStaticProjectSnapshot: () => assertValidStaticProjectSnapshot,
68
+ assertValidVoiceHelpAvailability: () => assertValidVoiceHelpAvailability,
59
69
  calculateAssessment: () => calculateAssessment,
60
70
  validateAssessmentRubric: () => validateAssessmentRubric,
61
71
  validateLearningPath: () => validateLearningPath,
@@ -639,6 +649,164 @@ var JUNIOR_CODER_ROBOT_RESCUE_PATH_CURRENT = JUNIOR_CODER_ROBOT_RESCUE_PATH_V1_1
639
649
  // src/contracts.ts
640
650
  var MISSION_AUTHORING_CONTRACT_VERSION_V1 = "1.0.0";
641
651
 
652
+ // src/contextual-help.ts
653
+ var CONTEXTUAL_HELP_CONTRACT_VERSION_V1 = "1.0.0";
654
+ var CONTEXTUAL_HELP_KINDS_V1 = Object.freeze([
655
+ "command",
656
+ "visual-block",
657
+ "generated-code",
658
+ "assessment-diagnostic"
659
+ ]);
660
+ var CONTEXTUAL_VOICE_INTENTS_V1 = Object.freeze([
661
+ "describe-command",
662
+ "describe-inputs",
663
+ "show-example",
664
+ "explain-assessment-failure",
665
+ "suggest-next-experiment",
666
+ "repeat",
667
+ "stop",
668
+ "unresolved"
669
+ ]);
670
+ var CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1 = Object.freeze([
671
+ "open-help",
672
+ "insert-example",
673
+ "show-inputs",
674
+ "show-assessment-evidence",
675
+ "try-next-experiment",
676
+ "repeat",
677
+ "stop",
678
+ "type-question"
679
+ ]);
680
+ var OPAQUE_ID = /^[a-z0-9]+(?:[._:-][a-z0-9]+)*$/u;
681
+ var SEMVER = /^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?$/u;
682
+ var SHA_256 = /^sha256:[a-f0-9]{64}$/u;
683
+ var LOCALE = /^[a-z]{2,3}(?:-[A-Z]{2})?$/u;
684
+ var MEDIA_TYPE = /^audio\/[a-z0-9.+-]+$/u;
685
+ function containsDisallowedControlCharacter(value) {
686
+ return [...value].some((character) => {
687
+ const code = character.codePointAt(0) ?? 0;
688
+ return code <= 8 || code === 11 || code === 12 || code >= 14 && code <= 31 || code === 127;
689
+ });
690
+ }
691
+ function requireOpaqueId(value, label, maximum = 160) {
692
+ if (value.length === 0 || value.length > maximum || !OPAQUE_ID.test(value)) {
693
+ throw new Error(`${label} must be a bounded opaque identifier.`);
694
+ }
695
+ }
696
+ function requireSemver(value, label) {
697
+ if (!SEMVER.test(value)) {
698
+ throw new Error(`${label} must be an immutable semantic version.`);
699
+ }
700
+ }
701
+ function requireIsoTimestamp(value, label) {
702
+ if (!/^\d{4}-\d{2}-\d{2}T/u.test(value) || !Number.isFinite(Date.parse(value))) {
703
+ throw new Error(`${label} must be an ISO-8601 timestamp.`);
704
+ }
705
+ }
706
+ function assertValidContextualHelpIdentifier(value) {
707
+ if (value.schemaVersion !== "1") {
708
+ throw new Error("schemaVersion must be 1.");
709
+ }
710
+ if (value.contractVersion !== CONTEXTUAL_HELP_CONTRACT_VERSION_V1) {
711
+ throw new Error("contractVersion is unsupported.");
712
+ }
713
+ if (!CONTEXTUAL_HELP_KINDS_V1.includes(value.kind)) {
714
+ throw new Error("kind is unsupported.");
715
+ }
716
+ requireOpaqueId(value.moduleId, "moduleId");
717
+ requireSemver(value.moduleVersion, "moduleVersion");
718
+ requireSemver(value.manifestVersion, "manifestVersion");
719
+ requireOpaqueId(value.helpId, "helpId");
720
+ }
721
+ function assertValidGuardianVoiceConsent(value) {
722
+ if (value.schemaVersion !== "1") {
723
+ throw new Error("schemaVersion must be 1.");
724
+ }
725
+ requireOpaqueId(value.actorAccountId, "actorAccountId");
726
+ requireOpaqueId(value.subjectAccountId, "subjectAccountId");
727
+ requireSemver(value.policyVersion, "policyVersion");
728
+ if (value.state !== "granted" && value.state !== "withdrawn") {
729
+ throw new Error("state must be granted or withdrawn.");
730
+ }
731
+ if (value.permittedProcessingRoute !== "private-edge-only") {
732
+ throw new Error("permittedProcessingRoute must be private-edge-only.");
733
+ }
734
+ requireIsoTimestamp(value.recordedAt, "recordedAt");
735
+ }
736
+ function assertValidContextualVoiceQuestionMetadata(value) {
737
+ assertValidContextualHelpIdentifier(value.help);
738
+ if (!LOCALE.test(value.locale)) throw new Error("locale is invalid.");
739
+ if (!MEDIA_TYPE.test(value.mediaType)) throw new Error("mediaType is invalid.");
740
+ if (!Number.isInteger(value.durationMs) || value.durationMs < 1 || value.durationMs > 1e4) {
741
+ throw new Error("durationMs must be between 1 and 10000.");
742
+ }
743
+ if (!Number.isInteger(value.audioBytes) || value.audioBytes < 1 || value.audioBytes > 2097152) {
744
+ throw new Error("audioBytes must be between 1 and 2097152.");
745
+ }
746
+ if (value.assessmentEvidenceId !== void 0) {
747
+ requireOpaqueId(value.assessmentEvidenceId, "assessmentEvidenceId");
748
+ }
749
+ }
750
+ function assertValidVoiceHelpAvailability(value) {
751
+ if (value.schemaVersion !== "1") throw new Error("schemaVersion must be 1.");
752
+ if (value.questionProcessingRoute !== "private-edge-only") {
753
+ throw new Error("questionProcessingRoute must be private-edge-only.");
754
+ }
755
+ if (value.guardianVoiceConsentRequired !== true) {
756
+ throw new Error("guardianVoiceConsentRequired must remain true.");
757
+ }
758
+ if (value.maximumRecordingDurationMs !== 1e4 || value.maximumAudioBytes !== 2097152 || value.rawAudioRetention !== "request-memory-only" || value.transcriptRetention !== "request-memory-only") {
759
+ throw new Error("Voice limits and transient retention are immutable in version one.");
760
+ }
761
+ if (value.microphoneAvailable !== (value.microphoneReason === "available")) {
762
+ throw new Error("microphoneReason must match microphone availability.");
763
+ }
764
+ }
765
+ function assertValidCanonicalSpokenHelpDescriptor(value) {
766
+ if (value.schemaVersion !== "1") throw new Error("schemaVersion must be 1.");
767
+ assertValidContextualHelpIdentifier(value.help);
768
+ requireOpaqueId(value.canonicalTextId, "canonicalTextId");
769
+ if (!SHA_256.test(value.authoritativeTextDigest)) {
770
+ throw new Error("authoritativeTextDigest must be a SHA-256 digest.");
771
+ }
772
+ if (!LOCALE.test(value.locale)) throw new Error("locale is invalid.");
773
+ requireOpaqueId(value.voiceProfile, "voiceProfile");
774
+ requireSemver(value.pronunciationVersion, "pronunciationVersion");
775
+ if (value.utteranceClass !== "system-generic" || value.sharingScope !== "global" || value.reuse !== "exact-only" || value.containsPersonalData !== false || value.containsLearnerContent !== false) {
776
+ throw new Error("Canonical spoken help must use global system-generic exact-only reuse.");
777
+ }
778
+ }
779
+ function assertValidContextualVoiceQuestionResult(value) {
780
+ if (value.schemaVersion !== "1") throw new Error("schemaVersion must be 1.");
781
+ assertValidContextualHelpIdentifier(value.help);
782
+ if (!CONTEXTUAL_VOICE_INTENTS_V1.includes(value.intent)) {
783
+ throw new Error("intent is unsupported.");
784
+ }
785
+ if (value.transcriptRetention !== "request-memory-only") {
786
+ throw new Error("transcriptRetention must be request-memory-only.");
787
+ }
788
+ if (value.status !== "resolved" && value.status !== "suggestions" && value.status !== "stopped") {
789
+ throw new Error("status is unsupported.");
790
+ }
791
+ if (value.transientTranscript.length > 500 || containsDisallowedControlCharacter(value.transientTranscript)) {
792
+ throw new Error("transientTranscript is invalid.");
793
+ }
794
+ if (value.answer) {
795
+ requireOpaqueId(value.answer.helpId, "answer.helpId");
796
+ if (value.answer.source !== "module-documentation" && value.answer.source !== "assessment-evidence" && value.answer.source !== "authored-fallback" || value.answer.text.length === 0 || value.answer.text.length > 800 || containsDisallowedControlCharacter(value.answer.text)) {
797
+ throw new Error("answer.text is invalid.");
798
+ }
799
+ }
800
+ if (value.suggestedActions.some(
801
+ (action) => !CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1.includes(action)
802
+ )) {
803
+ throw new Error("suggestedActions contains an unsupported action.");
804
+ }
805
+ if (value.mayAssignScore !== false || value.mayAwardReward !== false || value.mayPublish !== false || value.mayControlHardware !== false) {
806
+ throw new Error("mayAssignScore, reward, publish and hardware authority must remain false.");
807
+ }
808
+ }
809
+
642
810
  // src/publishing.ts
643
811
  var STATIC_PROJECT_PUBLISHING_CONTRACT_VERSION_V1 = "1.0.0";
644
812
  var STATIC_PROJECT_SCANNER_VERSION_V1 = "junior-coder-static-scan-v1";
@@ -6828,6 +6996,10 @@ ${summary}`);
6828
6996
  0 && (module.exports = {
6829
6997
  ADVENTURE_MISSION_PLANNER_MISSION_ONE_AUTHORING_V1,
6830
6998
  BEACON_BOT_MISSION_ONE_AUTHORING_V1,
6999
+ CONTEXTUAL_HELP_CONTRACT_VERSION_V1,
7000
+ CONTEXTUAL_HELP_KINDS_V1,
7001
+ CONTEXTUAL_VOICE_INTENTS_V1,
7002
+ CONTEXTUAL_VOICE_SUGGESTED_ACTIONS_V1,
6831
7003
  CREATURE_CARE_DASHBOARD_MISSION_ONE_AUTHORING_V1,
6832
7004
  DANCE_ROVER_MISSION_ONE_AUTHORING_V1,
6833
7005
  JUNIOR_CODER_MISSION_STAGE_ORDER_V1,
@@ -6857,11 +7029,17 @@ ${summary}`);
6857
7029
  VIBE_BUG_DETECTIVE_MISSION_ONE_AUTHORING_V1,
6858
7030
  VIBE_GAME_REMIX_LAB_MISSION_ONE_AUTHORING_V1,
6859
7031
  VIBE_IDEA_STUDIO_MISSION_ONE_AUTHORING_V1,
7032
+ assertValidCanonicalSpokenHelpDescriptor,
7033
+ assertValidContextualHelpIdentifier,
7034
+ assertValidContextualVoiceQuestionMetadata,
7035
+ assertValidContextualVoiceQuestionResult,
7036
+ assertValidGuardianVoiceConsent,
6860
7037
  assertValidLearningPath,
6861
7038
  assertValidMissionAuthoringBundle,
6862
7039
  assertValidStaticProjectGuardianApproval,
6863
7040
  assertValidStaticProjectPublication,
6864
7041
  assertValidStaticProjectSnapshot,
7042
+ assertValidVoiceHelpAvailability,
6865
7043
  calculateAssessment,
6866
7044
  validateAssessmentRubric,
6867
7045
  validateLearningPath,