@nakafa/aksara-contracts 0.27.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 +185 -0
- package/README.md +73 -0
- package/dist/artifact/integrity.d.ts +29 -0
- package/dist/artifact/integrity.js +54 -0
- package/dist/artifact/limits.d.ts +26 -0
- package/dist/artifact/limits.js +46 -0
- package/dist/artifact/source.d.ts +21 -0
- package/dist/artifact/source.js +24 -0
- package/dist/artifact/spec.d.ts +148 -0
- package/dist/artifact/spec.js +92 -0
- package/dist/artifact/verify.d.ts +26 -0
- package/dist/artifact/verify.js +18 -0
- package/dist/cache/content.d.ts +52 -0
- package/dist/cache/content.js +80 -0
- package/dist/content.d.ts +168 -0
- package/dist/content.js +107 -0
- package/dist/country.d.ts +4 -0
- package/dist/country.js +8 -0
- package/dist/date.d.ts +24 -0
- package/dist/date.js +44 -0
- package/dist/decode.d.ts +4 -0
- package/dist/decode.js +8 -0
- package/dist/delivery.d.ts +4 -0
- package/dist/delivery.js +7 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +8 -0
- package/dist/graph/family.d.ts +45 -0
- package/dist/graph/family.js +85 -0
- package/dist/graph/identity.d.ts +19 -0
- package/dist/graph/identity.js +39 -0
- package/dist/graph/spec.d.ts +20 -0
- package/dist/graph/spec.js +26 -0
- package/dist/hash/text.d.ts +10 -0
- package/dist/hash/text.js +20 -0
- package/dist/ids.d.ts +27 -0
- package/dist/ids.js +63 -0
- package/dist/limits.d.ts +10 -0
- package/dist/limits.js +10 -0
- package/dist/locale.d.ts +37 -0
- package/dist/locale.js +53 -0
- package/dist/material/domain.d.ts +4 -0
- package/dist/material/domain.js +8 -0
- package/dist/preview/artifact.d.ts +198 -0
- package/dist/preview/artifact.js +20 -0
- package/dist/preview/auth.d.ts +71 -0
- package/dist/preview/auth.js +49 -0
- package/dist/preview/document.d.ts +692 -0
- package/dist/preview/document.js +90 -0
- package/dist/preview/spec.d.ts +2517 -0
- package/dist/preview/spec.js +142 -0
- package/dist/preview/target.d.ts +172 -0
- package/dist/preview/target.js +70 -0
- package/dist/program/curriculum.d.ts +69 -0
- package/dist/program/curriculum.js +181 -0
- package/dist/program/snapshot/digest.d.ts +26 -0
- package/dist/program/snapshot/digest.js +220 -0
- package/dist/program/snapshot/hash.d.ts +31 -0
- package/dist/program/snapshot/hash.js +40 -0
- package/dist/program/snapshot/row-hash.d.ts +155 -0
- package/dist/program/snapshot/row-hash.js +34 -0
- package/dist/program/snapshot/row.d.ts +139 -0
- package/dist/program/snapshot/row.js +28 -0
- package/dist/program/snapshot/spec.d.ts +36 -0
- package/dist/program/snapshot/spec.js +39 -0
- package/dist/program/spec.d.ts +85 -0
- package/dist/program/spec.js +197 -0
- package/dist/projection/article.d.ts +145 -0
- package/dist/projection/article.js +150 -0
- package/dist/projection/digest.d.ts +261 -0
- package/dist/projection/digest.js +59 -0
- package/dist/projection/hash.d.ts +3 -0
- package/dist/projection/hash.js +10 -0
- package/dist/projection/material.d.ts +109 -0
- package/dist/projection/material.js +160 -0
- package/dist/projection/page.d.ts +112 -0
- package/dist/projection/page.js +106 -0
- package/dist/projection/question.d.ts +520 -0
- package/dist/projection/question.js +109 -0
- package/dist/projection/spec.d.ts +503 -0
- package/dist/projection/spec.js +56 -0
- package/dist/projection/verify.d.ts +281 -0
- package/dist/projection/verify.js +86 -0
- package/dist/question/identity.d.ts +175 -0
- package/dist/question/identity.js +205 -0
- package/dist/question/item.d.ts +453 -0
- package/dist/question/item.js +146 -0
- package/dist/question/response.d.ts +175 -0
- package/dist/question/response.js +150 -0
- package/dist/quran/attribution.d.ts +48 -0
- package/dist/quran/attribution.js +44 -0
- package/dist/quran/identity.d.ts +105 -0
- package/dist/quran/identity.js +100 -0
- package/dist/quran/notes.d.ts +56 -0
- package/dist/quran/notes.js +142 -0
- package/dist/quran/provenance.d.ts +257 -0
- package/dist/quran/provenance.js +144 -0
- package/dist/quran/snapshot/digest.d.ts +33 -0
- package/dist/quran/snapshot/digest.js +232 -0
- package/dist/quran/snapshot/hash.d.ts +51 -0
- package/dist/quran/snapshot/hash.js +50 -0
- package/dist/quran/snapshot/row/hash.d.ts +748 -0
- package/dist/quran/snapshot/row/hash.js +115 -0
- package/dist/quran/snapshot/row.d.ts +354 -0
- package/dist/quran/snapshot/row.js +91 -0
- package/dist/quran/snapshot/spec.d.ts +50 -0
- package/dist/quran/snapshot/spec.js +66 -0
- package/dist/quran/source.d.ts +206 -0
- package/dist/quran/source.js +153 -0
- package/dist/quran/spec.d.ts +47 -0
- package/dist/quran/spec.js +41 -0
- package/dist/quran/text.d.ts +7 -0
- package/dist/quran/text.js +11 -0
- package/dist/release/canonical.d.ts +23 -0
- package/dist/release/canonical.js +29 -0
- package/dist/release/current/evidence.d.ts +424 -0
- package/dist/release/current/evidence.js +41 -0
- package/dist/release/current/runtime.d.ts +4 -0
- package/dist/release/current/runtime.js +15 -0
- package/dist/release/current/state.d.ts +567 -0
- package/dist/release/current/state.js +91 -0
- package/dist/release/digest.d.ts +54 -0
- package/dist/release/digest.js +71 -0
- package/dist/release/hash.d.ts +72 -0
- package/dist/release/hash.js +14 -0
- package/dist/release/head.d.ts +206 -0
- package/dist/release/head.js +131 -0
- package/dist/release/items.d.ts +98 -0
- package/dist/release/items.js +114 -0
- package/dist/release/lifecycle.d.ts +297 -0
- package/dist/release/lifecycle.js +97 -0
- package/dist/release/origin.d.ts +30 -0
- package/dist/release/origin.js +24 -0
- package/dist/release/policy.d.ts +45 -0
- package/dist/release/policy.js +124 -0
- package/dist/release/result/digest.d.ts +127 -0
- package/dist/release/result/digest.js +140 -0
- package/dist/release/result/spec.d.ts +4 -0
- package/dist/release/result/spec.js +5 -0
- package/dist/release/rollback/digest.d.ts +126 -0
- package/dist/release/rollback/digest.js +92 -0
- package/dist/release/rollback/spec.d.ts +1729 -0
- package/dist/release/rollback/spec.js +156 -0
- package/dist/release/route/digest.d.ts +45 -0
- package/dist/release/route/digest.js +59 -0
- package/dist/release/route/page.d.ts +56 -0
- package/dist/release/route/page.js +46 -0
- package/dist/release/route/spec.d.ts +49 -0
- package/dist/release/route/spec.js +40 -0
- package/dist/release/route/verify.d.ts +66 -0
- package/dist/release/route/verify.js +67 -0
- package/dist/release/signing.d.ts +6 -0
- package/dist/release/signing.js +38 -0
- package/dist/release/snapshot/data.d.ts +475 -0
- package/dist/release/snapshot/data.js +74 -0
- package/dist/release/snapshot/evidence-requirement.d.ts +22 -0
- package/dist/release/snapshot/evidence-requirement.js +21 -0
- package/dist/release/snapshot/evidence.d.ts +58 -0
- package/dist/release/snapshot/evidence.js +14 -0
- package/dist/release/snapshot/program-evidence.d.ts +17 -0
- package/dist/release/snapshot/program-evidence.js +39 -0
- package/dist/release/snapshot/quran-evidence.d.ts +27 -0
- package/dist/release/snapshot/quran-evidence.js +43 -0
- package/dist/release/snapshot/scope.d.ts +60 -0
- package/dist/release/snapshot/scope.js +102 -0
- package/dist/release/snapshot/spec.d.ts +193 -0
- package/dist/release/snapshot/spec.js +139 -0
- package/dist/release/snapshot/tryout-evidence.d.ts +22 -0
- package/dist/release/snapshot/tryout-evidence.js +86 -0
- package/dist/release/snapshot/verify.d.ts +710 -0
- package/dist/release/snapshot/verify.js +147 -0
- package/dist/release/spec.d.ts +491 -0
- package/dist/release/spec.js +200 -0
- package/dist/release/verify.d.ts +98 -0
- package/dist/release/verify.js +70 -0
- package/dist/renderer/compatibility.d.ts +35 -0
- package/dist/renderer/compatibility.js +48 -0
- package/dist/renderer/component.d.ts +99 -0
- package/dist/renderer/component.js +102 -0
- package/dist/renderer/contract.d.ts +144 -0
- package/dist/renderer/contract.js +122 -0
- package/dist/renderer/domain.d.ts +6 -0
- package/dist/renderer/domain.js +18 -0
- package/dist/renderer/manifest.d.ts +86 -0
- package/dist/renderer/manifest.js +49 -0
- package/dist/renderer/selection.d.ts +39 -0
- package/dist/renderer/selection.js +87 -0
- package/dist/runtime/error.d.ts +8 -0
- package/dist/runtime/error.js +20 -0
- package/dist/runtime/protected/limits.d.ts +10 -0
- package/dist/runtime/protected/limits.js +15 -0
- package/dist/runtime/protected/spec.d.ts +328 -0
- package/dist/runtime/protected/spec.js +79 -0
- package/dist/runtime/protected/verify.d.ts +97 -0
- package/dist/runtime/protected/verify.js +72 -0
- package/dist/runtime/result.d.ts +12 -0
- package/dist/runtime/result.js +17 -0
- package/dist/runtime/spec.d.ts +705 -0
- package/dist/runtime/spec.js +61 -0
- package/dist/runtime/verify.d.ts +479 -0
- package/dist/runtime/verify.js +123 -0
- package/dist/signature/spec.d.ts +53 -0
- package/dist/signature/spec.js +29 -0
- package/dist/signature/trusted.d.ts +19 -0
- package/dist/signature/trusted.js +32 -0
- package/dist/signature/verify.d.ts +10 -0
- package/dist/signature/verify.js +82 -0
- package/dist/text/order.d.ts +2 -0
- package/dist/text/order.js +10 -0
- package/dist/text/syntax.d.ts +6 -0
- package/dist/text/syntax.js +15 -0
- package/dist/transport/batch.d.ts +971 -0
- package/dist/transport/batch.js +101 -0
- package/dist/transport/failure.d.ts +168 -0
- package/dist/transport/failure.js +194 -0
- package/dist/transport/group.d.ts +2946 -0
- package/dist/transport/group.js +47 -0
- package/dist/transport/limits.d.ts +30 -0
- package/dist/transport/limits.js +30 -0
- package/dist/transport/request.d.ts +9333 -0
- package/dist/transport/request.js +121 -0
- package/dist/transport/response.d.ts +6247 -0
- package/dist/transport/response.js +181 -0
- package/dist/transport/runtime.d.ts +77 -0
- package/dist/transport/runtime.js +33 -0
- package/dist/transport/snapshot.d.ts +978 -0
- package/dist/transport/snapshot.js +70 -0
- package/dist/tryout/canonical.d.ts +2 -0
- package/dist/tryout/canonical.js +11 -0
- package/dist/tryout/catalog-hash.d.ts +134 -0
- package/dist/tryout/catalog-hash.js +203 -0
- package/dist/tryout/catalog.d.ts +376 -0
- package/dist/tryout/catalog.js +147 -0
- package/dist/tryout/content-hash.d.ts +89 -0
- package/dist/tryout/content-hash.js +72 -0
- package/dist/tryout/identity.d.ts +12 -0
- package/dist/tryout/identity.js +46 -0
- package/dist/tryout/key.d.ts +4 -0
- package/dist/tryout/key.js +8 -0
- package/dist/tryout/language.d.ts +24 -0
- package/dist/tryout/language.js +32 -0
- package/dist/tryout/locale-closure.d.ts +20 -0
- package/dist/tryout/locale-closure.js +164 -0
- package/dist/tryout/placement-closure.d.ts +5 -0
- package/dist/tryout/placement-closure.js +30 -0
- package/dist/tryout/placement-hash.d.ts +11 -0
- package/dist/tryout/placement-hash.js +55 -0
- package/dist/tryout/placement.d.ts +280 -0
- package/dist/tryout/placement.js +82 -0
- package/dist/tryout/row-hash.d.ts +28 -0
- package/dist/tryout/row-hash.js +50 -0
- package/dist/tryout/runtime/canonical.d.ts +8 -0
- package/dist/tryout/runtime/canonical.js +33 -0
- package/dist/tryout/runtime/hash.d.ts +32 -0
- package/dist/tryout/runtime/hash.js +11 -0
- package/dist/tryout/runtime/source.d.ts +42 -0
- package/dist/tryout/runtime/source.js +47 -0
- package/dist/tryout/runtime/spec.d.ts +58 -0
- package/dist/tryout/runtime/spec.js +21 -0
- package/dist/tryout/runtime/verify.d.ts +61 -0
- package/dist/tryout/runtime/verify.js +69 -0
- package/dist/tryout/snapshot/canonical.d.ts +7 -0
- package/dist/tryout/snapshot/canonical.js +17 -0
- package/dist/tryout/snapshot/hash.d.ts +8 -0
- package/dist/tryout/snapshot/hash.js +19 -0
- package/dist/tryout/snapshot/spec.d.ts +46 -0
- package/dist/tryout/snapshot/spec.js +30 -0
- package/dist/tryout/spec.d.ts +13 -0
- package/dist/tryout/spec.js +15 -0
- package/package.json +506 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { GitCommitShaSchema, Sha256HashSchema } from "#contracts/ids";
|
|
3
|
+
import { PreviewArtifactSchema } from "#contracts/preview/artifact";
|
|
4
|
+
import { PreviewDocumentSchema, } from "#contracts/preview/document";
|
|
5
|
+
import { PreviewRouteSchema } from "#contracts/preview/target";
|
|
6
|
+
/** Stable protocol implemented by the loopback-only authoring provider. */
|
|
7
|
+
export const LOCAL_PREVIEW_FORMAT = "aksara-local-preview";
|
|
8
|
+
/** Exact Git evidence printed and served for one participating checkout. */
|
|
9
|
+
export const PreviewRepositorySchema = Schema.Struct({
|
|
10
|
+
dirty: Schema.Boolean,
|
|
11
|
+
sha: GitCommitShaSchema,
|
|
12
|
+
});
|
|
13
|
+
const PreviewArtifactListSchema = Schema.NonEmptyArray(PreviewArtifactSchema).pipe(Schema.check(Schema.makeFilter((artifacts) => artifacts.length <= 2, {
|
|
14
|
+
message: "Expected at most two preview artifacts.",
|
|
15
|
+
})));
|
|
16
|
+
/** Checks one article projection against its selected registry route. */
|
|
17
|
+
function matchesArticleDocument(document, projection) {
|
|
18
|
+
return (projection.kind === "article" &&
|
|
19
|
+
projection.articleSlug === document.route.articleSlug &&
|
|
20
|
+
projection.category === document.route.category &&
|
|
21
|
+
projection.contentKey === document.route.contentKey &&
|
|
22
|
+
projection.appLocale === document.route.appLocale &&
|
|
23
|
+
projection.artifactLocale === document.route.artifactLocale &&
|
|
24
|
+
projection.publicPath === document.route.publicPath);
|
|
25
|
+
}
|
|
26
|
+
/** Checks one material projection against its selected registry route. */
|
|
27
|
+
function matchesMaterialDocument(document, projection) {
|
|
28
|
+
return (projection.kind === "subject-lesson" &&
|
|
29
|
+
projection.contentKey === document.route.contentKey &&
|
|
30
|
+
projection.appLocale === document.route.appLocale &&
|
|
31
|
+
projection.artifactLocale === document.route.artifactLocale &&
|
|
32
|
+
projection.materialKey === document.route.materialKey &&
|
|
33
|
+
projection.order === document.route.order &&
|
|
34
|
+
projection.publicPath === document.route.publicPath &&
|
|
35
|
+
projection.sectionKey === document.route.sectionKey &&
|
|
36
|
+
projection.topicTitle === document.route.topicTitle);
|
|
37
|
+
}
|
|
38
|
+
/** Checks one page projection against its selected registry route. */
|
|
39
|
+
function matchesPageDocument(document, projection) {
|
|
40
|
+
return (projection.kind === "public-page" &&
|
|
41
|
+
projection.contentKey === document.route.contentKey &&
|
|
42
|
+
projection.appLocale === document.route.appLocale &&
|
|
43
|
+
projection.artifactLocale === document.route.artifactLocale &&
|
|
44
|
+
projection.pageKey === document.route.pageKey &&
|
|
45
|
+
projection.publicPath === document.route.publicPath &&
|
|
46
|
+
projection.sourcePath === document.sourcePath);
|
|
47
|
+
}
|
|
48
|
+
/** Checks one question projection against an exact prompt or answer identity. */
|
|
49
|
+
function matchesQuestionIdentity(projection, identity) {
|
|
50
|
+
return (projection.kind === "question-body" &&
|
|
51
|
+
projection.bodyKind === identity.bodyKind &&
|
|
52
|
+
projection.contentKey === identity.contentKey &&
|
|
53
|
+
projection.artifactLocale === identity.artifactLocale &&
|
|
54
|
+
projection.peerContentKey === identity.peerContentKey &&
|
|
55
|
+
projection.questionKey === identity.questionKey &&
|
|
56
|
+
projection.questionNumber === identity.questionNumber &&
|
|
57
|
+
projection.setKey === identity.setKey);
|
|
58
|
+
}
|
|
59
|
+
/** Checks the ordered prompt required before one entitled answer artifact. */
|
|
60
|
+
function matchesAnswerPrompt(projection, input) {
|
|
61
|
+
const { identity } = input;
|
|
62
|
+
return (projection.kind === "question-body" &&
|
|
63
|
+
projection.bodyKind === "question" &&
|
|
64
|
+
projection.contentKey === identity.peerContentKey &&
|
|
65
|
+
projection.artifactLocale === input.questionArtifactLocale &&
|
|
66
|
+
projection.peerContentKey === identity.contentKey &&
|
|
67
|
+
projection.questionKey === identity.questionKey &&
|
|
68
|
+
projection.questionNumber === identity.questionNumber &&
|
|
69
|
+
projection.setKey === identity.setKey);
|
|
70
|
+
}
|
|
71
|
+
/** Checks family-specific artifact count, order, identity, and projection kind. */
|
|
72
|
+
function hasCoherentReadyArtifacts(input) {
|
|
73
|
+
const first = input.artifacts[0].projection;
|
|
74
|
+
if (input.document.family === "article") {
|
|
75
|
+
return (input.artifacts.length === 1 &&
|
|
76
|
+
matchesArticleDocument(input.document, first));
|
|
77
|
+
}
|
|
78
|
+
if (input.document.family === "material") {
|
|
79
|
+
return (input.artifacts.length === 1 &&
|
|
80
|
+
matchesMaterialDocument(input.document, first));
|
|
81
|
+
}
|
|
82
|
+
if (input.document.family === "page") {
|
|
83
|
+
return (input.artifacts.length === 1 && matchesPageDocument(input.document, first));
|
|
84
|
+
}
|
|
85
|
+
if (input.document.identity.bodyKind === "question") {
|
|
86
|
+
return (input.artifacts.length === 1 &&
|
|
87
|
+
matchesQuestionIdentity(first, input.document.identity));
|
|
88
|
+
}
|
|
89
|
+
const second = input.artifacts[1]?.projection;
|
|
90
|
+
return (input.artifacts.length === 2 &&
|
|
91
|
+
matchesAnswerPrompt(first, {
|
|
92
|
+
identity: input.document.identity,
|
|
93
|
+
questionArtifactLocale: input.document.target.placement.questionArtifactLocale,
|
|
94
|
+
}) &&
|
|
95
|
+
second !== undefined &&
|
|
96
|
+
matchesQuestionIdentity(second, input.document.identity));
|
|
97
|
+
}
|
|
98
|
+
const PreviewBaseSchema = Schema.Struct({
|
|
99
|
+
document: PreviewDocumentSchema,
|
|
100
|
+
format: Schema.Literal(LOCAL_PREVIEW_FORMAT),
|
|
101
|
+
repositories: Schema.Struct({
|
|
102
|
+
aksara: PreviewRepositorySchema,
|
|
103
|
+
nakafa: PreviewRepositorySchema,
|
|
104
|
+
}),
|
|
105
|
+
revision: Schema.Finite.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThan(0))),
|
|
106
|
+
});
|
|
107
|
+
/** A changed route is intentionally unavailable while its source compiles. */
|
|
108
|
+
export const PreviewPendingSchema = Schema.Struct({
|
|
109
|
+
...PreviewBaseSchema.fields,
|
|
110
|
+
status: Schema.Literal("pending"),
|
|
111
|
+
});
|
|
112
|
+
/** Exact bounded artifacts available to the real loopback Nakafa app. */
|
|
113
|
+
export const PreviewReadySchema = Schema.Struct({
|
|
114
|
+
...PreviewBaseSchema.fields,
|
|
115
|
+
artifacts: PreviewArtifactListSchema,
|
|
116
|
+
rendererManifestHash: Sha256HashSchema,
|
|
117
|
+
status: Schema.Literal("ready"),
|
|
118
|
+
}).pipe(Schema.check(Schema.makeFilter(hasCoherentReadyArtifacts, {
|
|
119
|
+
message: "Expected preview artifacts to match the selected document exactly.",
|
|
120
|
+
})));
|
|
121
|
+
/** Sanitized compile failure that forbids fallback to an older route body. */
|
|
122
|
+
export const PreviewFailedSchema = Schema.Struct({
|
|
123
|
+
...PreviewBaseSchema.fields,
|
|
124
|
+
failure: Schema.Struct({
|
|
125
|
+
code: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(128))),
|
|
126
|
+
message: Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.isMaxLength(512))),
|
|
127
|
+
}),
|
|
128
|
+
status: Schema.Literal("failed"),
|
|
129
|
+
});
|
|
130
|
+
/** Complete current local source state returned by the manifest endpoint. */
|
|
131
|
+
export const LocalPreviewManifestSchema = Schema.Union([
|
|
132
|
+
PreviewPendingSchema,
|
|
133
|
+
PreviewReadySchema,
|
|
134
|
+
PreviewFailedSchema,
|
|
135
|
+
]);
|
|
136
|
+
/** Minimal SSE signal carrying only the derived route and manifest revision. */
|
|
137
|
+
export const PreviewEventSchema = Schema.Struct({
|
|
138
|
+
format: Schema.Literal(LOCAL_PREVIEW_FORMAT),
|
|
139
|
+
revision: Schema.Finite.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThan(0))),
|
|
140
|
+
route: PreviewRouteSchema,
|
|
141
|
+
status: Schema.Literals(["pending", "ready", "failed"]),
|
|
142
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
/** Locale and existing public route refreshed by one preview update. */
|
|
3
|
+
export declare const PreviewRouteSchema: Schema.Struct<{
|
|
4
|
+
readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
5
|
+
readonly publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Placement identity needed to select one preview body.
|
|
9
|
+
* Responses remain owned by the canonical question registry and projections.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TryoutPreviewPlacementSchema: Schema.Struct<{
|
|
12
|
+
readonly answerArtifactLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraArtifactLocale">;
|
|
13
|
+
readonly answerContentKey: Schema.brand<Schema.String, "@NakafaAI/AksaraContentKey">;
|
|
14
|
+
readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
15
|
+
readonly blueprint: Schema.optionalKey<Schema.Struct<{
|
|
16
|
+
readonly cognitiveLevel: Schema.String;
|
|
17
|
+
readonly contentDomain: Schema.String;
|
|
18
|
+
readonly topic: Schema.String;
|
|
19
|
+
}>>;
|
|
20
|
+
readonly countryKey: Schema.String;
|
|
21
|
+
readonly deliveryLanguage: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraDeliveryLanguage">;
|
|
22
|
+
readonly examKey: Schema.String;
|
|
23
|
+
readonly languagePolicy: Schema.Union<readonly [Schema.Struct<{
|
|
24
|
+
readonly kind: Schema.Literal<"app-locale">;
|
|
25
|
+
}>, Schema.Struct<{
|
|
26
|
+
readonly kind: Schema.Literal<"fixed">;
|
|
27
|
+
readonly language: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraDeliveryLanguage">;
|
|
28
|
+
}>]>;
|
|
29
|
+
readonly questionArtifactLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraArtifactLocale">;
|
|
30
|
+
readonly questionContentKey: Schema.brand<Schema.String, "@NakafaAI/AksaraContentKey">;
|
|
31
|
+
readonly questionOrder: Schema.Finite;
|
|
32
|
+
readonly questionSourcePath: Schema.brand<Schema.String, "@NakafaAI/AksaraCorpusSourcePath">;
|
|
33
|
+
readonly rendererDomain: Schema.Literals<readonly ["ai-ds", "biology", "chemistry", "mathematics", "physics", "politics", "site", "snbt-general", "snbt-math", "snbt-plain", "snbt-quant", "tka-math"]>;
|
|
34
|
+
readonly scope: Schema.Literal<"server">;
|
|
35
|
+
readonly sectionKey: Schema.String;
|
|
36
|
+
readonly setKey: Schema.String;
|
|
37
|
+
readonly sourceRevision: Schema.Trimmed;
|
|
38
|
+
readonly stimulusKey: Schema.optionalKey<Schema.String>;
|
|
39
|
+
readonly trackKey: Schema.String;
|
|
40
|
+
}>;
|
|
41
|
+
/** Exact current try-out rows needed to preview one authored question body. */
|
|
42
|
+
export declare const TryoutPreviewTargetSchema: Schema.Struct<{
|
|
43
|
+
readonly exam: Schema.Struct<{
|
|
44
|
+
readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
45
|
+
readonly description: Schema.optional<Schema.String>;
|
|
46
|
+
readonly graph: Schema.Struct<{
|
|
47
|
+
readonly alignmentId: Schema.String;
|
|
48
|
+
readonly assetId: Schema.String;
|
|
49
|
+
readonly conceptId: Schema.String;
|
|
50
|
+
readonly learningObjectId: Schema.String;
|
|
51
|
+
readonly lensId: Schema.String;
|
|
52
|
+
}>;
|
|
53
|
+
readonly sourceRevision: Schema.Trimmed;
|
|
54
|
+
readonly title: Schema.String;
|
|
55
|
+
readonly countryKey: Schema.String;
|
|
56
|
+
readonly examKey: Schema.String;
|
|
57
|
+
readonly kind: Schema.Literal<"exam">;
|
|
58
|
+
readonly order: Schema.Finite;
|
|
59
|
+
readonly publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
|
|
60
|
+
readonly scoringStrategy: Schema.Literals<readonly ["irt", "raw"]>;
|
|
61
|
+
}>;
|
|
62
|
+
readonly placement: Schema.Struct<{
|
|
63
|
+
readonly answerArtifactLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraArtifactLocale">;
|
|
64
|
+
readonly answerContentKey: Schema.brand<Schema.String, "@NakafaAI/AksaraContentKey">;
|
|
65
|
+
readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
66
|
+
readonly blueprint: Schema.optionalKey<Schema.Struct<{
|
|
67
|
+
readonly cognitiveLevel: Schema.String;
|
|
68
|
+
readonly contentDomain: Schema.String;
|
|
69
|
+
readonly topic: Schema.String;
|
|
70
|
+
}>>;
|
|
71
|
+
readonly countryKey: Schema.String;
|
|
72
|
+
readonly deliveryLanguage: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraDeliveryLanguage">;
|
|
73
|
+
readonly examKey: Schema.String;
|
|
74
|
+
readonly languagePolicy: Schema.Union<readonly [Schema.Struct<{
|
|
75
|
+
readonly kind: Schema.Literal<"app-locale">;
|
|
76
|
+
}>, Schema.Struct<{
|
|
77
|
+
readonly kind: Schema.Literal<"fixed">;
|
|
78
|
+
readonly language: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraDeliveryLanguage">;
|
|
79
|
+
}>]>;
|
|
80
|
+
readonly questionArtifactLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraArtifactLocale">;
|
|
81
|
+
readonly questionContentKey: Schema.brand<Schema.String, "@NakafaAI/AksaraContentKey">;
|
|
82
|
+
readonly questionOrder: Schema.Finite;
|
|
83
|
+
readonly questionSourcePath: Schema.brand<Schema.String, "@NakafaAI/AksaraCorpusSourcePath">;
|
|
84
|
+
readonly rendererDomain: Schema.Literals<readonly ["ai-ds", "biology", "chemistry", "mathematics", "physics", "politics", "site", "snbt-general", "snbt-math", "snbt-plain", "snbt-quant", "tka-math"]>;
|
|
85
|
+
readonly scope: Schema.Literal<"server">;
|
|
86
|
+
readonly sectionKey: Schema.String;
|
|
87
|
+
readonly setKey: Schema.String;
|
|
88
|
+
readonly sourceRevision: Schema.Trimmed;
|
|
89
|
+
readonly stimulusKey: Schema.optionalKey<Schema.String>;
|
|
90
|
+
readonly trackKey: Schema.String;
|
|
91
|
+
}>;
|
|
92
|
+
readonly section: Schema.Struct<{
|
|
93
|
+
readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
94
|
+
readonly description: Schema.optional<Schema.String>;
|
|
95
|
+
readonly graph: Schema.Struct<{
|
|
96
|
+
readonly alignmentId: Schema.String;
|
|
97
|
+
readonly assetId: Schema.String;
|
|
98
|
+
readonly conceptId: Schema.String;
|
|
99
|
+
readonly learningObjectId: Schema.String;
|
|
100
|
+
readonly lensId: Schema.String;
|
|
101
|
+
}>;
|
|
102
|
+
readonly sourceRevision: Schema.Trimmed;
|
|
103
|
+
readonly title: Schema.String;
|
|
104
|
+
readonly countryKey: Schema.String;
|
|
105
|
+
readonly examKey: Schema.String;
|
|
106
|
+
readonly kind: Schema.Literal<"section">;
|
|
107
|
+
readonly order: Schema.Finite;
|
|
108
|
+
readonly publicPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
109
|
+
readonly questionCount: Schema.Finite;
|
|
110
|
+
readonly questionSourcePath: Schema.brand<Schema.String, "@NakafaAI/AksaraCorpusSourcePath">;
|
|
111
|
+
readonly sectionKey: Schema.String;
|
|
112
|
+
readonly setKey: Schema.String;
|
|
113
|
+
readonly timeLimitSeconds: Schema.Finite;
|
|
114
|
+
readonly trackKey: Schema.String;
|
|
115
|
+
readonly visibility: Schema.Literals<readonly ["internal-entry", "visible"]>;
|
|
116
|
+
}>;
|
|
117
|
+
readonly set: Schema.Struct<{
|
|
118
|
+
readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
119
|
+
readonly description: Schema.optional<Schema.String>;
|
|
120
|
+
readonly graph: Schema.Struct<{
|
|
121
|
+
readonly alignmentId: Schema.String;
|
|
122
|
+
readonly assetId: Schema.String;
|
|
123
|
+
readonly conceptId: Schema.String;
|
|
124
|
+
readonly learningObjectId: Schema.String;
|
|
125
|
+
readonly lensId: Schema.String;
|
|
126
|
+
}>;
|
|
127
|
+
readonly sourceRevision: Schema.Trimmed;
|
|
128
|
+
readonly title: Schema.String;
|
|
129
|
+
readonly countryKey: Schema.String;
|
|
130
|
+
readonly examKey: Schema.String;
|
|
131
|
+
readonly internalEntrySectionKey: Schema.optional<Schema.String>;
|
|
132
|
+
readonly kind: Schema.Literal<"set">;
|
|
133
|
+
readonly order: Schema.Finite;
|
|
134
|
+
readonly publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
|
|
135
|
+
readonly questionCount: Schema.Finite;
|
|
136
|
+
readonly scoringStrategy: Schema.Literals<readonly ["irt", "raw"]>;
|
|
137
|
+
readonly sectionCount: Schema.Finite;
|
|
138
|
+
readonly setKey: Schema.String;
|
|
139
|
+
readonly trackKey: Schema.String;
|
|
140
|
+
readonly visibleSectionCount: Schema.Finite;
|
|
141
|
+
}>;
|
|
142
|
+
readonly track: Schema.Struct<{
|
|
143
|
+
readonly appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
144
|
+
readonly description: Schema.optional<Schema.String>;
|
|
145
|
+
readonly graph: Schema.Struct<{
|
|
146
|
+
readonly alignmentId: Schema.String;
|
|
147
|
+
readonly assetId: Schema.String;
|
|
148
|
+
readonly conceptId: Schema.String;
|
|
149
|
+
readonly learningObjectId: Schema.String;
|
|
150
|
+
readonly lensId: Schema.String;
|
|
151
|
+
}>;
|
|
152
|
+
readonly sourceRevision: Schema.Trimmed;
|
|
153
|
+
readonly title: Schema.String;
|
|
154
|
+
readonly countryKey: Schema.String;
|
|
155
|
+
readonly examKey: Schema.String;
|
|
156
|
+
readonly kind: Schema.Literal<"track">;
|
|
157
|
+
readonly order: Schema.Finite;
|
|
158
|
+
readonly publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
|
|
159
|
+
readonly questionCount: Schema.Finite;
|
|
160
|
+
readonly sectionCount: Schema.Finite;
|
|
161
|
+
readonly setCount: Schema.Finite;
|
|
162
|
+
readonly trackKey: Schema.String;
|
|
163
|
+
readonly trackKind: Schema.Literals<readonly ["subject", "year"]>;
|
|
164
|
+
readonly visibleSectionCount: Schema.Finite;
|
|
165
|
+
}>;
|
|
166
|
+
}>;
|
|
167
|
+
export type TryoutPreviewTarget = typeof TryoutPreviewTargetSchema.Type;
|
|
168
|
+
/** Derives the existing try-out page used by the trusted authoring preview. */
|
|
169
|
+
export declare function previewTryoutRoute(target: TryoutPreviewTarget): {
|
|
170
|
+
readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
|
|
171
|
+
readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
|
|
172
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Schema, Struct } from "effect";
|
|
2
|
+
import { PublicPathSchema } from "#contracts/ids";
|
|
3
|
+
import { AppLocaleSchema } from "#contracts/locale";
|
|
4
|
+
import { TryoutExamSchema, TryoutSectionSchema, TryoutSetSchema, TryoutTrackSchema, } from "#contracts/tryout/catalog";
|
|
5
|
+
import { TryoutPlacementSourceSchema } from "#contracts/tryout/placement";
|
|
6
|
+
/** Locale and existing public route refreshed by one preview update. */
|
|
7
|
+
export const PreviewRouteSchema = Schema.Struct({
|
|
8
|
+
appLocale: AppLocaleSchema,
|
|
9
|
+
publicPath: PublicPathSchema,
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Placement identity needed to select one preview body.
|
|
13
|
+
* Responses remain owned by the canonical question registry and projections.
|
|
14
|
+
*/
|
|
15
|
+
export const TryoutPreviewPlacementSchema = TryoutPlacementSourceSchema.pipe((schema) => schema.mapFields(Struct.omit(["response"])));
|
|
16
|
+
/** Checks that every target row belongs to one locale-specific hierarchy. */
|
|
17
|
+
function hasCoherentTryoutHierarchy(input) {
|
|
18
|
+
const rows = [input.track, input.set, input.section, input.placement];
|
|
19
|
+
return (rows.every(({ countryKey }) => countryKey === input.exam.countryKey) &&
|
|
20
|
+
rows.every(({ examKey }) => examKey === input.exam.examKey) &&
|
|
21
|
+
rows.every(({ appLocale }) => appLocale === input.exam.appLocale) &&
|
|
22
|
+
rows.every(({ sourceRevision }) => sourceRevision === input.exam.sourceRevision) &&
|
|
23
|
+
input.set.trackKey === input.track.trackKey &&
|
|
24
|
+
input.section.trackKey === input.track.trackKey &&
|
|
25
|
+
input.placement.trackKey === input.track.trackKey &&
|
|
26
|
+
input.section.setKey === input.set.setKey &&
|
|
27
|
+
input.placement.setKey === input.set.setKey &&
|
|
28
|
+
input.placement.sectionKey === input.section.sectionKey);
|
|
29
|
+
}
|
|
30
|
+
/** Checks nested routes and the internal-entry route fallback contract. */
|
|
31
|
+
function hasCoherentTryoutRoutes(input) {
|
|
32
|
+
if (!input.track.publicPath.startsWith(`${input.exam.publicPath}/`)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (!input.set.publicPath.startsWith(`${input.track.publicPath}/`)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (input.section.visibility === "internal-entry") {
|
|
39
|
+
return input.set.internalEntrySectionKey === input.section.sectionKey;
|
|
40
|
+
}
|
|
41
|
+
return (input.set.internalEntrySectionKey === undefined &&
|
|
42
|
+
input.section.publicPath?.startsWith(`${input.set.publicPath}/`) === true);
|
|
43
|
+
}
|
|
44
|
+
/** Checks that the selected placement is owned by the target section source. */
|
|
45
|
+
function hasCoherentTryoutPlacement(input) {
|
|
46
|
+
return (input.placement.questionOrder <= input.section.questionCount &&
|
|
47
|
+
input.placement.questionSourcePath ===
|
|
48
|
+
`${input.section.questionSourcePath}/question-${input.placement.questionOrder}`);
|
|
49
|
+
}
|
|
50
|
+
/** Exact current try-out rows needed to preview one authored question body. */
|
|
51
|
+
export const TryoutPreviewTargetSchema = Schema.Struct({
|
|
52
|
+
exam: TryoutExamSchema,
|
|
53
|
+
placement: TryoutPreviewPlacementSchema,
|
|
54
|
+
section: TryoutSectionSchema,
|
|
55
|
+
set: TryoutSetSchema,
|
|
56
|
+
track: TryoutTrackSchema,
|
|
57
|
+
}).pipe(Schema.check(Schema.makeFilter(hasCoherentTryoutHierarchy, {
|
|
58
|
+
message: "Expected preview target hierarchy keys, locale, and revision to agree.",
|
|
59
|
+
})), Schema.check(Schema.makeFilter(hasCoherentTryoutRoutes, {
|
|
60
|
+
message: "Expected preview target routes to form one reachable hierarchy.",
|
|
61
|
+
})), Schema.check(Schema.makeFilter(hasCoherentTryoutPlacement, {
|
|
62
|
+
message: "Expected preview placement to belong to the selected section source.",
|
|
63
|
+
})));
|
|
64
|
+
/** Derives the existing try-out page used by the trusted authoring preview. */
|
|
65
|
+
export function previewTryoutRoute(target) {
|
|
66
|
+
return PreviewRouteSchema.make({
|
|
67
|
+
appLocale: target.section.appLocale,
|
|
68
|
+
publicPath: target.section.publicPath ?? target.set.publicPath,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { type AppLocale } from "#contracts/locale";
|
|
3
|
+
/** Localized public route namespaces shared by projection and verification. */
|
|
4
|
+
export declare const CURRICULUM_NAMESPACES: {
|
|
5
|
+
readonly de: "lehrplaene";
|
|
6
|
+
readonly en: "curriculum";
|
|
7
|
+
readonly id: "kurikulum";
|
|
8
|
+
};
|
|
9
|
+
/** Resolves the route namespace owned by one application locale. */
|
|
10
|
+
export declare function curriculumNamespace(appLocale: AppLocale): "curriculum" | "kurikulum" | "lehrplaene";
|
|
11
|
+
/** Checks whether one curriculum level owns a learner-renderable route. */
|
|
12
|
+
export declare const isRenderableCurriculumLevel: <I>(input: I) => input is I & ("class" | "course" | "stage" | "subject" | "track");
|
|
13
|
+
/** Stable source-owned identity for one curriculum tree node. */
|
|
14
|
+
export declare const CurriculumNodeKeySchema: Schema.brand<Schema.String, "@NakafaAI/AksaraCurriculumNodeKey">;
|
|
15
|
+
export type CurriculumNodeKey = typeof CurriculumNodeKeySchema.Type;
|
|
16
|
+
/** Localized curriculum route before material presentation context is added. */
|
|
17
|
+
export declare const CurriculumRouteDraftSchema: Schema.Struct<{
|
|
18
|
+
appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
19
|
+
canonicalPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
20
|
+
displayGroupIconKey: Schema.optional<Schema.Literals<readonly ["advanced", "assessment", "certificate", "course", "diploma", "early-years", "global-education", "grade-1", "grade-10", "grade-11", "grade-12", "grade-2", "grade-3", "grade-4", "grade-5", "grade-6", "grade-7", "grade-8", "grade-9", "high-school", "mathematics", "middle-school", "primary-school", "science", "school", "standards", "state"]>>;
|
|
21
|
+
displayGroupTitle: Schema.optional<Schema.String>;
|
|
22
|
+
iconKey: Schema.Literals<readonly ["advanced", "assessment", "certificate", "course", "diploma", "early-years", "global-education", "grade-1", "grade-10", "grade-11", "grade-12", "grade-2", "grade-3", "grade-4", "grade-5", "grade-6", "grade-7", "grade-8", "grade-9", "high-school", "mathematics", "middle-school", "primary-school", "science", "school", "standards", "state"]>;
|
|
23
|
+
kind: Schema.Literal<"curriculum-context">;
|
|
24
|
+
level: Schema.Literals<readonly ["class", "course", "domain", "lesson", "phase", "section", "set", "stage", "subject", "topic", "track", "unit"]>;
|
|
25
|
+
materialCardDescription: Schema.optional<Schema.String>;
|
|
26
|
+
materialCardTitle: Schema.optional<Schema.String>;
|
|
27
|
+
materialContextNodeKey: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraCurriculumNodeKey">>;
|
|
28
|
+
materialContextParentPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
29
|
+
materialContextPublicPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
30
|
+
materialDomain: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraMaterialDomain">>;
|
|
31
|
+
materialKey: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraMaterialKey">>;
|
|
32
|
+
nodeKey: Schema.String;
|
|
33
|
+
order: Schema.Int;
|
|
34
|
+
parentPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
35
|
+
programKey: Schema.brand<Schema.String, "@NakafaAI/AksaraLearningProgramKey">;
|
|
36
|
+
publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
|
|
37
|
+
sitemap: Schema.Boolean;
|
|
38
|
+
sourcePath: Schema.brand<Schema.String, "@NakafaAI/AksaraCorpusSourcePath">;
|
|
39
|
+
title: Schema.String;
|
|
40
|
+
}>;
|
|
41
|
+
export type CurriculumRouteDraft = typeof CurriculumRouteDraftSchema.Type;
|
|
42
|
+
/** Exact localized curriculum route stored inside the program snapshot. */
|
|
43
|
+
export declare const CurriculumRouteSchema: Schema.Struct<{
|
|
44
|
+
appLocale: Schema.brand<Schema.Literals<readonly ["en", "id", "de"]>, "@NakafaAI/AksaraAppLocale">;
|
|
45
|
+
canonicalPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
46
|
+
displayGroupIconKey: Schema.optional<Schema.Literals<readonly ["advanced", "assessment", "certificate", "course", "diploma", "early-years", "global-education", "grade-1", "grade-10", "grade-11", "grade-12", "grade-2", "grade-3", "grade-4", "grade-5", "grade-6", "grade-7", "grade-8", "grade-9", "high-school", "mathematics", "middle-school", "primary-school", "science", "school", "standards", "state"]>>;
|
|
47
|
+
displayGroupTitle: Schema.optional<Schema.String>;
|
|
48
|
+
iconKey: Schema.Literals<readonly ["advanced", "assessment", "certificate", "course", "diploma", "early-years", "global-education", "grade-1", "grade-10", "grade-11", "grade-12", "grade-2", "grade-3", "grade-4", "grade-5", "grade-6", "grade-7", "grade-8", "grade-9", "high-school", "mathematics", "middle-school", "primary-school", "science", "school", "standards", "state"]>;
|
|
49
|
+
kind: Schema.Literal<"curriculum-context">;
|
|
50
|
+
level: Schema.Literals<readonly ["class", "course", "domain", "lesson", "phase", "section", "set", "stage", "subject", "topic", "track", "unit"]>;
|
|
51
|
+
materialCardDescription: Schema.optional<Schema.String>;
|
|
52
|
+
materialCardTitle: Schema.optional<Schema.String>;
|
|
53
|
+
materialContextNodeKey: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraCurriculumNodeKey">>;
|
|
54
|
+
materialContextParentPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
55
|
+
materialContextPublicPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
56
|
+
materialDomain: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraMaterialDomain">>;
|
|
57
|
+
materialKey: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraMaterialKey">>;
|
|
58
|
+
nodeKey: Schema.String;
|
|
59
|
+
order: Schema.Int;
|
|
60
|
+
parentPath: Schema.optional<Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">>;
|
|
61
|
+
programKey: Schema.brand<Schema.String, "@NakafaAI/AksaraLearningProgramKey">;
|
|
62
|
+
publicPath: Schema.brand<Schema.String, "@NakafaAI/AksaraPublicPath">;
|
|
63
|
+
sitemap: Schema.Boolean;
|
|
64
|
+
sourcePath: Schema.brand<Schema.String, "@NakafaAI/AksaraCorpusSourcePath">;
|
|
65
|
+
title: Schema.String;
|
|
66
|
+
}>;
|
|
67
|
+
export type CurriculumRoute = typeof CurriculumRouteSchema.Type;
|
|
68
|
+
/** Serializes one curriculum route in stable signed field order. */
|
|
69
|
+
export declare function canonicalizeCurriculumRoute(route: CurriculumRoute): string;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { CorpusSourcePathSchema, PublicPathSchema } from "#contracts/ids";
|
|
3
|
+
import { AppLocaleSchema } from "#contracts/locale";
|
|
4
|
+
import { MaterialDomainSchema } from "#contracts/material/domain";
|
|
5
|
+
import { LearningProgramKeySchema, ProgramNavigationIconKeySchema, ProgramNavigationLevelSchema, } from "#contracts/program/spec";
|
|
6
|
+
import { MaterialKeySchema } from "#contracts/projection/material";
|
|
7
|
+
import { isLowerKebab } from "#contracts/text/syntax";
|
|
8
|
+
const CurriculumNamespaceMapSchema = Schema.Struct({
|
|
9
|
+
de: Schema.Literal("lehrplaene"),
|
|
10
|
+
en: Schema.Literal("curriculum"),
|
|
11
|
+
id: Schema.Literal("kurikulum"),
|
|
12
|
+
});
|
|
13
|
+
const RenderableCurriculumLevelSchema = Schema.Literals([
|
|
14
|
+
"class",
|
|
15
|
+
"course",
|
|
16
|
+
"stage",
|
|
17
|
+
"subject",
|
|
18
|
+
"track",
|
|
19
|
+
]);
|
|
20
|
+
/** Checks one curriculum route key with its optional canonical root suffix. */
|
|
21
|
+
function isCurriculumRouteNodeKey(value) {
|
|
22
|
+
const rootSuffix = ":root";
|
|
23
|
+
return isLowerKebab(value.endsWith(rootSuffix) ? value.slice(0, -rootSuffix.length) : value);
|
|
24
|
+
}
|
|
25
|
+
/** Localized public route namespaces shared by projection and verification. */
|
|
26
|
+
export const CURRICULUM_NAMESPACES = CurriculumNamespaceMapSchema.make({
|
|
27
|
+
de: "lehrplaene",
|
|
28
|
+
en: "curriculum",
|
|
29
|
+
id: "kurikulum",
|
|
30
|
+
});
|
|
31
|
+
/** Resolves the route namespace owned by one application locale. */
|
|
32
|
+
export function curriculumNamespace(appLocale) {
|
|
33
|
+
const localeCode = String(appLocale);
|
|
34
|
+
if (localeCode === "de") {
|
|
35
|
+
return CURRICULUM_NAMESPACES.de;
|
|
36
|
+
}
|
|
37
|
+
if (localeCode === "id") {
|
|
38
|
+
return CURRICULUM_NAMESPACES.id;
|
|
39
|
+
}
|
|
40
|
+
return CURRICULUM_NAMESPACES.en;
|
|
41
|
+
}
|
|
42
|
+
/** Checks whether one curriculum level owns a learner-renderable route. */
|
|
43
|
+
export const isRenderableCurriculumLevel = Schema.is(RenderableCurriculumLevelSchema);
|
|
44
|
+
/** Stable source-owned identity for one curriculum tree node. */
|
|
45
|
+
export const CurriculumNodeKeySchema = Schema.String.pipe(Schema.check(Schema.makeFilter(isLowerKebab, {
|
|
46
|
+
description: "Lowercase kebab-case curriculum node key.",
|
|
47
|
+
identifier: "CurriculumNodeKey",
|
|
48
|
+
message: "Invalid curriculum node key.",
|
|
49
|
+
})), Schema.brand("@NakafaAI/AksaraCurriculumNodeKey"));
|
|
50
|
+
const CurriculumRouteNodeKeySchema = Schema.String.pipe(Schema.check(Schema.makeFilter(isCurriculumRouteNodeKey)));
|
|
51
|
+
const CurriculumRouteFields = {
|
|
52
|
+
appLocale: AppLocaleSchema,
|
|
53
|
+
canonicalPath: Schema.optional(PublicPathSchema),
|
|
54
|
+
displayGroupIconKey: Schema.optional(ProgramNavigationIconKeySchema),
|
|
55
|
+
displayGroupTitle: Schema.optional(Schema.String),
|
|
56
|
+
iconKey: ProgramNavigationIconKeySchema,
|
|
57
|
+
kind: Schema.Literal("curriculum-context"),
|
|
58
|
+
level: ProgramNavigationLevelSchema,
|
|
59
|
+
materialCardDescription: Schema.optional(Schema.String),
|
|
60
|
+
materialCardTitle: Schema.optional(Schema.String),
|
|
61
|
+
materialContextNodeKey: Schema.optional(CurriculumNodeKeySchema),
|
|
62
|
+
materialContextParentPath: Schema.optional(PublicPathSchema),
|
|
63
|
+
materialContextPublicPath: Schema.optional(PublicPathSchema),
|
|
64
|
+
materialDomain: Schema.optional(MaterialDomainSchema),
|
|
65
|
+
materialKey: Schema.optional(MaterialKeySchema),
|
|
66
|
+
nodeKey: CurriculumRouteNodeKeySchema,
|
|
67
|
+
order: Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
68
|
+
parentPath: Schema.optional(PublicPathSchema),
|
|
69
|
+
programKey: LearningProgramKeySchema,
|
|
70
|
+
publicPath: PublicPathSchema,
|
|
71
|
+
sitemap: Schema.Boolean,
|
|
72
|
+
sourcePath: CorpusSourcePathSchema,
|
|
73
|
+
title: Schema.String,
|
|
74
|
+
};
|
|
75
|
+
/** Returns the direct parent of one canonical public path. */
|
|
76
|
+
function parentPath(publicPath) {
|
|
77
|
+
return publicPath.slice(0, publicPath.lastIndexOf("/"));
|
|
78
|
+
}
|
|
79
|
+
/** Checks namespace, root, and direct-parent route ownership. */
|
|
80
|
+
function hasCoherentRoute(input) {
|
|
81
|
+
const namespace = `${curriculumNamespace(input.appLocale)}/`;
|
|
82
|
+
if (!input.publicPath.startsWith(namespace)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (input.sourcePath !== `packages/corpus/curriculum/${input.programKey}`) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (input.parentPath === undefined) {
|
|
89
|
+
return (input.level === "track" &&
|
|
90
|
+
input.nodeKey === `${input.programKey}:root` &&
|
|
91
|
+
input.publicPath.split("/").length === 2);
|
|
92
|
+
}
|
|
93
|
+
return (input.nodeKey !== `${input.programKey}:root` &&
|
|
94
|
+
input.parentPath === parentPath(input.publicPath));
|
|
95
|
+
}
|
|
96
|
+
/** Checks all-or-nothing material and context ownership fields. */
|
|
97
|
+
function hasCoherentMaterialLink(input) {
|
|
98
|
+
const ownsMaterial = input.materialKey !== undefined;
|
|
99
|
+
const ownsCanonical = input.canonicalPath !== undefined;
|
|
100
|
+
const contextCount = [
|
|
101
|
+
input.materialContextNodeKey,
|
|
102
|
+
input.materialContextParentPath,
|
|
103
|
+
input.materialContextPublicPath,
|
|
104
|
+
].filter((value) => value !== undefined).length;
|
|
105
|
+
if (!ownsMaterial) {
|
|
106
|
+
return !ownsCanonical && contextCount === 0;
|
|
107
|
+
}
|
|
108
|
+
return ownsCanonical && (contextCount === 0 || contextCount === 3);
|
|
109
|
+
}
|
|
110
|
+
/** Checks that every published material has its complete presentation context. */
|
|
111
|
+
function hasCompleteMaterialContext(input) {
|
|
112
|
+
if (input.materialKey === undefined) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return (input.materialContextNodeKey !== undefined &&
|
|
116
|
+
input.materialContextParentPath !== undefined &&
|
|
117
|
+
input.materialContextPublicPath !== undefined);
|
|
118
|
+
}
|
|
119
|
+
/** Checks that only learner-renderable levels enter the sitemap. */
|
|
120
|
+
function hasCoherentSitemap(input) {
|
|
121
|
+
return !input.sitemap || isRenderableCurriculumLevel(input.level);
|
|
122
|
+
}
|
|
123
|
+
/** Localized curriculum route before material presentation context is added. */
|
|
124
|
+
export const CurriculumRouteDraftSchema = Schema.Struct(CurriculumRouteFields).pipe(Schema.check(Schema.makeFilter(hasCoherentRoute, {
|
|
125
|
+
message: "Expected coherent localized curriculum route ownership.",
|
|
126
|
+
})), Schema.check(Schema.makeFilter(hasCoherentMaterialLink, {
|
|
127
|
+
message: "Expected coherent curriculum material ownership.",
|
|
128
|
+
})), Schema.check(Schema.makeFilter(hasCoherentSitemap, {
|
|
129
|
+
message: "Expected only renderable curriculum sitemap routes.",
|
|
130
|
+
})));
|
|
131
|
+
/** Exact localized curriculum route stored inside the program snapshot. */
|
|
132
|
+
export const CurriculumRouteSchema = CurriculumRouteDraftSchema.pipe(Schema.check(Schema.makeFilter(hasCompleteMaterialContext, {
|
|
133
|
+
message: "Expected complete curriculum material context ownership.",
|
|
134
|
+
})));
|
|
135
|
+
/** Serializes one curriculum route in stable signed field order. */
|
|
136
|
+
export function canonicalizeCurriculumRoute(route) {
|
|
137
|
+
return JSON.stringify({
|
|
138
|
+
appLocale: route.appLocale,
|
|
139
|
+
...(route.canonicalPath === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { canonicalPath: route.canonicalPath }),
|
|
142
|
+
...(route.displayGroupIconKey === undefined
|
|
143
|
+
? {}
|
|
144
|
+
: { displayGroupIconKey: route.displayGroupIconKey }),
|
|
145
|
+
...(route.displayGroupTitle === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { displayGroupTitle: route.displayGroupTitle }),
|
|
148
|
+
iconKey: route.iconKey,
|
|
149
|
+
kind: route.kind,
|
|
150
|
+
level: route.level,
|
|
151
|
+
...(route.materialCardDescription === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: { materialCardDescription: route.materialCardDescription }),
|
|
154
|
+
...(route.materialCardTitle === undefined
|
|
155
|
+
? {}
|
|
156
|
+
: { materialCardTitle: route.materialCardTitle }),
|
|
157
|
+
...(route.materialContextNodeKey === undefined
|
|
158
|
+
? {}
|
|
159
|
+
: { materialContextNodeKey: route.materialContextNodeKey }),
|
|
160
|
+
...(route.materialContextParentPath === undefined
|
|
161
|
+
? {}
|
|
162
|
+
: { materialContextParentPath: route.materialContextParentPath }),
|
|
163
|
+
...(route.materialContextPublicPath === undefined
|
|
164
|
+
? {}
|
|
165
|
+
: { materialContextPublicPath: route.materialContextPublicPath }),
|
|
166
|
+
...(route.materialDomain === undefined
|
|
167
|
+
? {}
|
|
168
|
+
: { materialDomain: route.materialDomain }),
|
|
169
|
+
...(route.materialKey === undefined
|
|
170
|
+
? {}
|
|
171
|
+
: { materialKey: route.materialKey }),
|
|
172
|
+
nodeKey: route.nodeKey,
|
|
173
|
+
order: route.order,
|
|
174
|
+
...(route.parentPath === undefined ? {} : { parentPath: route.parentPath }),
|
|
175
|
+
programKey: route.programKey,
|
|
176
|
+
publicPath: route.publicPath,
|
|
177
|
+
sitemap: route.sitemap,
|
|
178
|
+
sourcePath: route.sourcePath,
|
|
179
|
+
title: route.title,
|
|
180
|
+
});
|
|
181
|
+
}
|