@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,70 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { ReleaseIdSchema, Sha256HashSchema } from "#contracts/ids";
|
|
3
|
+
import { ContentSnapshotManifestSchema, ContentSnapshotRowSchema, } from "#contracts/release/snapshot/data";
|
|
4
|
+
import { ContentSnapshotKindSchema } from "#contracts/release/snapshot/scope";
|
|
5
|
+
import { MAX_SNAPSHOT_BATCH_COUNT } from "#contracts/transport/limits";
|
|
6
|
+
const BatchIndexSchema = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
7
|
+
const BatchCountSchema = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
8
|
+
const OutcomeCountSchema = Schema.Literals([0, 1]);
|
|
9
|
+
const StageSnapshotFields = {
|
|
10
|
+
releaseId: ReleaseIdSchema,
|
|
11
|
+
snapshot: ContentSnapshotManifestSchema,
|
|
12
|
+
};
|
|
13
|
+
/** Canonical input for staging one immutable structured-family manifest. */
|
|
14
|
+
export const StageSnapshotInputSchema = Schema.Struct(StageSnapshotFields);
|
|
15
|
+
/** Stages one immutable structured-family manifest before any of its rows. */
|
|
16
|
+
export const StageSnapshotRequestSchema = Schema.Struct({
|
|
17
|
+
...StageSnapshotFields,
|
|
18
|
+
operation: Schema.Literal("stageSnapshot"),
|
|
19
|
+
});
|
|
20
|
+
const SnapshotRowBatchSchema = Schema.NonEmptyArray(ContentSnapshotRowSchema).pipe(Schema.check(Schema.isMaxLength(MAX_SNAPSHOT_BATCH_COUNT)));
|
|
21
|
+
const StageSnapshotBatchFields = {
|
|
22
|
+
batchIndex: BatchIndexSchema,
|
|
23
|
+
family: ContentSnapshotKindSchema,
|
|
24
|
+
releaseId: ReleaseIdSchema,
|
|
25
|
+
rows: SnapshotRowBatchSchema,
|
|
26
|
+
snapshotId: Sha256HashSchema,
|
|
27
|
+
};
|
|
28
|
+
/** Checks that every row belongs to the envelope and Quran snapshot identity. */
|
|
29
|
+
function hasBoundSnapshotRows(input) {
|
|
30
|
+
return input.rows.every((row) => {
|
|
31
|
+
if (row.family !== input.family) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (row.family !== "quran") {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return row.record.snapshotId === input.snapshotId;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const BoundSnapshotBatchSchema = Schema.Struct(StageSnapshotBatchFields).pipe(Schema.check(Schema.makeFilter(hasBoundSnapshotRows, {
|
|
41
|
+
message: "Expected snapshot rows bound to one family and snapshot identity.",
|
|
42
|
+
})));
|
|
43
|
+
/** Canonical input for one bounded structured-snapshot row batch. */
|
|
44
|
+
export const StageSnapshotBatchInputSchema = BoundSnapshotBatchSchema;
|
|
45
|
+
/** Stages one non-empty bounded structured-snapshot row batch. */
|
|
46
|
+
export const StageSnapshotBatchRequestSchema = Schema.Struct({
|
|
47
|
+
...StageSnapshotBatchFields,
|
|
48
|
+
operation: Schema.Literal("stageSnapshotBatch"),
|
|
49
|
+
}).pipe(Schema.check(Schema.makeFilter(hasBoundSnapshotRows, {
|
|
50
|
+
message: "Expected snapshot rows bound to one family and snapshot identity.",
|
|
51
|
+
})));
|
|
52
|
+
/** Idempotent outcome bound to one exact structured-snapshot row batch. */
|
|
53
|
+
export const StageSnapshotBatchReceiptSchema = Schema.Struct({
|
|
54
|
+
batchIndex: BatchIndexSchema,
|
|
55
|
+
created: BatchCountSchema,
|
|
56
|
+
family: ContentSnapshotKindSchema,
|
|
57
|
+
releaseId: ReleaseIdSchema,
|
|
58
|
+
snapshotId: Sha256HashSchema,
|
|
59
|
+
unchanged: BatchCountSchema,
|
|
60
|
+
});
|
|
61
|
+
/** Idempotent outcome for staging exactly one family manifest. */
|
|
62
|
+
export const StageSnapshotReceiptSchema = Schema.Struct({
|
|
63
|
+
created: OutcomeCountSchema,
|
|
64
|
+
family: ContentSnapshotKindSchema,
|
|
65
|
+
releaseId: ReleaseIdSchema,
|
|
66
|
+
snapshotId: Sha256HashSchema,
|
|
67
|
+
unchanged: OutcomeCountSchema,
|
|
68
|
+
}).pipe(Schema.check(Schema.makeFilter(({ created, unchanged }) => created + unchanged === 1, {
|
|
69
|
+
message: "Expected exactly one created or unchanged snapshot manifest.",
|
|
70
|
+
})));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Sha256HashSchema } from "#contracts/ids";
|
|
3
|
+
/** Computes one domain-separated canonical try-out SHA-256 identity. */
|
|
4
|
+
export function hashTryoutCanonical(domain, canonical) {
|
|
5
|
+
const digest = createHash("sha256")
|
|
6
|
+
.update(domain)
|
|
7
|
+
.update("\n")
|
|
8
|
+
.update(canonical)
|
|
9
|
+
.digest("hex");
|
|
10
|
+
return Sha256HashSchema.make(`sha256:${digest}`);
|
|
11
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Effect, type Stream } from "effect";
|
|
2
|
+
import { type TryoutCatalogRecord, type TryoutCatalogRow } from "#contracts/tryout/catalog";
|
|
3
|
+
/** Compares localized hierarchy rows in their canonical digest order. */
|
|
4
|
+
export declare function compareTryoutCatalog(left: TryoutCatalogRow, right: TryoutCatalogRow): -1 | 0 | 1;
|
|
5
|
+
/** Builds one locale-neutral hierarchy identity for closure checks. */
|
|
6
|
+
export declare function tryoutCatalogLogicalIdentity(row: TryoutCatalogRow): string;
|
|
7
|
+
/** Builds the locale-neutral catalog identity of one section reference. */
|
|
8
|
+
export declare function tryoutSectionLogicalIdentity(input: {
|
|
9
|
+
readonly countryKey: string;
|
|
10
|
+
readonly examKey: string;
|
|
11
|
+
readonly sectionKey: string;
|
|
12
|
+
readonly setKey: string;
|
|
13
|
+
readonly trackKey: string;
|
|
14
|
+
}): string;
|
|
15
|
+
/** Serializes locale-neutral catalog facts for cross-locale closure checks. */
|
|
16
|
+
export declare function canonicalizeTryoutCatalogFacts(row: TryoutCatalogRow): string;
|
|
17
|
+
/** Serializes one current hierarchy row in stable field order. */
|
|
18
|
+
export declare function canonicalizeTryoutCatalog(row: TryoutCatalogRow): string;
|
|
19
|
+
/** Creates one authenticated current hierarchy record. */
|
|
20
|
+
export declare function makeTryoutCatalogRecord(row: TryoutCatalogRow): TryoutCatalogRecord;
|
|
21
|
+
/** Digests canonically ordered current hierarchy records. */
|
|
22
|
+
export declare const digestTryoutCatalog: <E, R>(records: Stream.Stream<{
|
|
23
|
+
readonly row: {
|
|
24
|
+
readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
|
|
25
|
+
readonly description?: string | undefined;
|
|
26
|
+
readonly graph: {
|
|
27
|
+
readonly alignmentId: string;
|
|
28
|
+
readonly assetId: string;
|
|
29
|
+
readonly conceptId: string;
|
|
30
|
+
readonly learningObjectId: string;
|
|
31
|
+
readonly lensId: string;
|
|
32
|
+
};
|
|
33
|
+
readonly sourceRevision: string;
|
|
34
|
+
readonly title: string;
|
|
35
|
+
readonly countryKey: string;
|
|
36
|
+
readonly examKey: string;
|
|
37
|
+
readonly kind: "track";
|
|
38
|
+
readonly order: number;
|
|
39
|
+
readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
|
|
40
|
+
readonly questionCount: number;
|
|
41
|
+
readonly sectionCount: number;
|
|
42
|
+
readonly setCount: number;
|
|
43
|
+
readonly trackKey: string;
|
|
44
|
+
readonly trackKind: "subject" | "year";
|
|
45
|
+
readonly visibleSectionCount: number;
|
|
46
|
+
} | {
|
|
47
|
+
readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
|
|
48
|
+
readonly description?: string | undefined;
|
|
49
|
+
readonly graph: {
|
|
50
|
+
readonly alignmentId: string;
|
|
51
|
+
readonly assetId: string;
|
|
52
|
+
readonly conceptId: string;
|
|
53
|
+
readonly learningObjectId: string;
|
|
54
|
+
readonly lensId: string;
|
|
55
|
+
};
|
|
56
|
+
readonly sourceRevision: string;
|
|
57
|
+
readonly title: string;
|
|
58
|
+
readonly countryKey: string;
|
|
59
|
+
readonly examKey: string;
|
|
60
|
+
readonly internalEntrySectionKey?: string | undefined;
|
|
61
|
+
readonly kind: "set";
|
|
62
|
+
readonly order: number;
|
|
63
|
+
readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
|
|
64
|
+
readonly questionCount: number;
|
|
65
|
+
readonly scoringStrategy: "irt" | "raw";
|
|
66
|
+
readonly sectionCount: number;
|
|
67
|
+
readonly setKey: string;
|
|
68
|
+
readonly trackKey: string;
|
|
69
|
+
readonly visibleSectionCount: number;
|
|
70
|
+
} | {
|
|
71
|
+
readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
|
|
72
|
+
readonly description?: string | undefined;
|
|
73
|
+
readonly graph: {
|
|
74
|
+
readonly alignmentId: string;
|
|
75
|
+
readonly assetId: string;
|
|
76
|
+
readonly conceptId: string;
|
|
77
|
+
readonly learningObjectId: string;
|
|
78
|
+
readonly lensId: string;
|
|
79
|
+
};
|
|
80
|
+
readonly sourceRevision: string;
|
|
81
|
+
readonly title: string;
|
|
82
|
+
readonly countryKey: string;
|
|
83
|
+
readonly examKey: string;
|
|
84
|
+
readonly kind: "section";
|
|
85
|
+
readonly order: number;
|
|
86
|
+
readonly publicPath?: (string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">) | undefined;
|
|
87
|
+
readonly questionCount: number;
|
|
88
|
+
readonly questionSourcePath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraCorpusSourcePath">;
|
|
89
|
+
readonly sectionKey: string;
|
|
90
|
+
readonly setKey: string;
|
|
91
|
+
readonly timeLimitSeconds: number;
|
|
92
|
+
readonly trackKey: string;
|
|
93
|
+
readonly visibility: "internal-entry" | "visible";
|
|
94
|
+
} | {
|
|
95
|
+
readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
|
|
96
|
+
readonly description?: string | undefined;
|
|
97
|
+
readonly graph: {
|
|
98
|
+
readonly alignmentId: string;
|
|
99
|
+
readonly assetId: string;
|
|
100
|
+
readonly conceptId: string;
|
|
101
|
+
readonly learningObjectId: string;
|
|
102
|
+
readonly lensId: string;
|
|
103
|
+
};
|
|
104
|
+
readonly sourceRevision: string;
|
|
105
|
+
readonly title: string;
|
|
106
|
+
readonly countryCode: string;
|
|
107
|
+
readonly countryKey: string;
|
|
108
|
+
readonly kind: "country";
|
|
109
|
+
readonly order: number;
|
|
110
|
+
readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
|
|
111
|
+
} | {
|
|
112
|
+
readonly appLocale: ("de" | "en" | "id") & import("effect/Brand").Brand<"@NakafaAI/AksaraAppLocale">;
|
|
113
|
+
readonly description?: string | undefined;
|
|
114
|
+
readonly graph: {
|
|
115
|
+
readonly alignmentId: string;
|
|
116
|
+
readonly assetId: string;
|
|
117
|
+
readonly conceptId: string;
|
|
118
|
+
readonly learningObjectId: string;
|
|
119
|
+
readonly lensId: string;
|
|
120
|
+
};
|
|
121
|
+
readonly sourceRevision: string;
|
|
122
|
+
readonly title: string;
|
|
123
|
+
readonly countryKey: string;
|
|
124
|
+
readonly examKey: string;
|
|
125
|
+
readonly kind: "exam";
|
|
126
|
+
readonly order: number;
|
|
127
|
+
readonly publicPath: string & import("effect/Brand").Brand<"@NakafaAI/AksaraPublicPath">;
|
|
128
|
+
readonly scoringStrategy: "irt" | "raw";
|
|
129
|
+
};
|
|
130
|
+
readonly rowHash: string & import("effect/Brand").Brand<"@NakafaAI/AksaraSha256Hash">;
|
|
131
|
+
}, E, R>) => Effect.Effect<{
|
|
132
|
+
count: number;
|
|
133
|
+
digest: string & import("effect/Brand").Brand<"@NakafaAI/AksaraSha256Hash">;
|
|
134
|
+
}, E | import("#contracts/tryout/row-hash").TryoutDigestError, R>;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { canonicalizeLearningGraphIdentity } from "#contracts/graph/spec";
|
|
3
|
+
import { compareCodeUnits } from "#contracts/text/order";
|
|
4
|
+
import { hashTryoutCanonical } from "#contracts/tryout/canonical";
|
|
5
|
+
import { TryoutCatalogRecordSchema, } from "#contracts/tryout/catalog";
|
|
6
|
+
import { tryoutCatalogIdentity } from "#contracts/tryout/identity";
|
|
7
|
+
import { digestTryoutRecords } from "#contracts/tryout/row-hash";
|
|
8
|
+
const CATALOG_DOMAIN = "nakafa.aksara.tryout-catalog";
|
|
9
|
+
/** Includes an optional field without serializing absence as null. */
|
|
10
|
+
function optionalField(key, value) {
|
|
11
|
+
return value === undefined ? {} : { [key]: value };
|
|
12
|
+
}
|
|
13
|
+
/** Compares localized hierarchy rows in their canonical digest order. */
|
|
14
|
+
export function compareTryoutCatalog(left, right) {
|
|
15
|
+
return compareCodeUnits(tryoutCatalogIdentity(left), tryoutCatalogIdentity(right));
|
|
16
|
+
}
|
|
17
|
+
/** Builds one locale-neutral hierarchy identity for closure checks. */
|
|
18
|
+
export function tryoutCatalogLogicalIdentity(row) {
|
|
19
|
+
return [
|
|
20
|
+
row.kind,
|
|
21
|
+
row.countryKey,
|
|
22
|
+
"examKey" in row ? row.examKey : "",
|
|
23
|
+
"trackKey" in row ? row.trackKey : "",
|
|
24
|
+
"setKey" in row ? row.setKey : "",
|
|
25
|
+
"sectionKey" in row ? row.sectionKey : "",
|
|
26
|
+
].join("\0");
|
|
27
|
+
}
|
|
28
|
+
/** Builds the locale-neutral catalog identity of one section reference. */
|
|
29
|
+
export function tryoutSectionLogicalIdentity(input) {
|
|
30
|
+
return [
|
|
31
|
+
"section",
|
|
32
|
+
input.countryKey,
|
|
33
|
+
input.examKey,
|
|
34
|
+
input.trackKey,
|
|
35
|
+
input.setKey,
|
|
36
|
+
input.sectionKey,
|
|
37
|
+
].join("\0");
|
|
38
|
+
}
|
|
39
|
+
/** Serializes locale-neutral catalog facts for cross-locale closure checks. */
|
|
40
|
+
export function canonicalizeTryoutCatalogFacts(row) {
|
|
41
|
+
const graph = {
|
|
42
|
+
alignmentId: row.graph.alignmentId,
|
|
43
|
+
conceptId: row.graph.conceptId,
|
|
44
|
+
learningObjectId: row.graph.learningObjectId,
|
|
45
|
+
lensId: row.graph.lensId,
|
|
46
|
+
};
|
|
47
|
+
const shared = {
|
|
48
|
+
graph,
|
|
49
|
+
kind: row.kind,
|
|
50
|
+
order: row.order,
|
|
51
|
+
sourceRevision: row.sourceRevision,
|
|
52
|
+
};
|
|
53
|
+
if (row.kind === "country") {
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
...shared,
|
|
56
|
+
countryCode: row.countryCode,
|
|
57
|
+
countryKey: row.countryKey,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (row.kind === "exam") {
|
|
61
|
+
return JSON.stringify({
|
|
62
|
+
...shared,
|
|
63
|
+
countryKey: row.countryKey,
|
|
64
|
+
examKey: row.examKey,
|
|
65
|
+
scoringStrategy: row.scoringStrategy,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (row.kind === "track") {
|
|
69
|
+
return JSON.stringify({
|
|
70
|
+
...shared,
|
|
71
|
+
countryKey: row.countryKey,
|
|
72
|
+
examKey: row.examKey,
|
|
73
|
+
questionCount: row.questionCount,
|
|
74
|
+
sectionCount: row.sectionCount,
|
|
75
|
+
setCount: row.setCount,
|
|
76
|
+
trackKey: row.trackKey,
|
|
77
|
+
trackKind: row.trackKind,
|
|
78
|
+
visibleSectionCount: row.visibleSectionCount,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (row.kind === "set") {
|
|
82
|
+
return JSON.stringify({
|
|
83
|
+
...shared,
|
|
84
|
+
countryKey: row.countryKey,
|
|
85
|
+
examKey: row.examKey,
|
|
86
|
+
...optionalField("internalEntrySectionKey", row.internalEntrySectionKey),
|
|
87
|
+
questionCount: row.questionCount,
|
|
88
|
+
scoringStrategy: row.scoringStrategy,
|
|
89
|
+
sectionCount: row.sectionCount,
|
|
90
|
+
setKey: row.setKey,
|
|
91
|
+
trackKey: row.trackKey,
|
|
92
|
+
visibleSectionCount: row.visibleSectionCount,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return JSON.stringify({
|
|
96
|
+
...shared,
|
|
97
|
+
countryKey: row.countryKey,
|
|
98
|
+
examKey: row.examKey,
|
|
99
|
+
questionCount: row.questionCount,
|
|
100
|
+
questionSourcePath: row.questionSourcePath,
|
|
101
|
+
sectionKey: row.sectionKey,
|
|
102
|
+
setKey: row.setKey,
|
|
103
|
+
timeLimitSeconds: row.timeLimitSeconds,
|
|
104
|
+
trackKey: row.trackKey,
|
|
105
|
+
visibility: row.visibility,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Serializes one current hierarchy row in stable field order. */
|
|
109
|
+
export function canonicalizeTryoutCatalog(row) {
|
|
110
|
+
const localized = {
|
|
111
|
+
appLocale: row.appLocale,
|
|
112
|
+
...optionalField("description", row.description),
|
|
113
|
+
graph: canonicalizeLearningGraphIdentity(row.graph),
|
|
114
|
+
sourceRevision: row.sourceRevision,
|
|
115
|
+
title: row.title,
|
|
116
|
+
};
|
|
117
|
+
if (row.kind === "country") {
|
|
118
|
+
return JSON.stringify({
|
|
119
|
+
...localized,
|
|
120
|
+
countryCode: row.countryCode,
|
|
121
|
+
countryKey: row.countryKey,
|
|
122
|
+
kind: row.kind,
|
|
123
|
+
order: row.order,
|
|
124
|
+
publicPath: row.publicPath,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (row.kind === "exam") {
|
|
128
|
+
return JSON.stringify({
|
|
129
|
+
...localized,
|
|
130
|
+
countryKey: row.countryKey,
|
|
131
|
+
examKey: row.examKey,
|
|
132
|
+
kind: row.kind,
|
|
133
|
+
order: row.order,
|
|
134
|
+
publicPath: row.publicPath,
|
|
135
|
+
scoringStrategy: row.scoringStrategy,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (row.kind === "track") {
|
|
139
|
+
return JSON.stringify({
|
|
140
|
+
...localized,
|
|
141
|
+
countryKey: row.countryKey,
|
|
142
|
+
examKey: row.examKey,
|
|
143
|
+
kind: row.kind,
|
|
144
|
+
order: row.order,
|
|
145
|
+
publicPath: row.publicPath,
|
|
146
|
+
questionCount: row.questionCount,
|
|
147
|
+
sectionCount: row.sectionCount,
|
|
148
|
+
setCount: row.setCount,
|
|
149
|
+
trackKey: row.trackKey,
|
|
150
|
+
trackKind: row.trackKind,
|
|
151
|
+
visibleSectionCount: row.visibleSectionCount,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (row.kind === "set") {
|
|
155
|
+
return JSON.stringify({
|
|
156
|
+
...localized,
|
|
157
|
+
countryKey: row.countryKey,
|
|
158
|
+
examKey: row.examKey,
|
|
159
|
+
...optionalField("internalEntrySectionKey", row.internalEntrySectionKey),
|
|
160
|
+
kind: row.kind,
|
|
161
|
+
order: row.order,
|
|
162
|
+
publicPath: row.publicPath,
|
|
163
|
+
questionCount: row.questionCount,
|
|
164
|
+
scoringStrategy: row.scoringStrategy,
|
|
165
|
+
sectionCount: row.sectionCount,
|
|
166
|
+
setKey: row.setKey,
|
|
167
|
+
trackKey: row.trackKey,
|
|
168
|
+
visibleSectionCount: row.visibleSectionCount,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return JSON.stringify({
|
|
172
|
+
...localized,
|
|
173
|
+
countryKey: row.countryKey,
|
|
174
|
+
examKey: row.examKey,
|
|
175
|
+
kind: row.kind,
|
|
176
|
+
order: row.order,
|
|
177
|
+
...optionalField("publicPath", row.publicPath),
|
|
178
|
+
questionCount: row.questionCount,
|
|
179
|
+
questionSourcePath: row.questionSourcePath,
|
|
180
|
+
sectionKey: row.sectionKey,
|
|
181
|
+
setKey: row.setKey,
|
|
182
|
+
timeLimitSeconds: row.timeLimitSeconds,
|
|
183
|
+
trackKey: row.trackKey,
|
|
184
|
+
visibility: row.visibility,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** Creates one authenticated current hierarchy record. */
|
|
188
|
+
export function makeTryoutCatalogRecord(row) {
|
|
189
|
+
return TryoutCatalogRecordSchema.make({
|
|
190
|
+
row,
|
|
191
|
+
rowHash: hashTryoutCanonical(CATALOG_DOMAIN, canonicalizeTryoutCatalog(row)),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Digests canonically ordered current hierarchy records. */
|
|
195
|
+
export const digestTryoutCatalog = Effect.fn("AksaraContracts.digestTryoutCatalog")(function* (records) {
|
|
196
|
+
return yield* digestTryoutRecords({
|
|
197
|
+
canonicalize: canonicalizeTryoutCatalog,
|
|
198
|
+
domain: CATALOG_DOMAIN,
|
|
199
|
+
identity: tryoutCatalogIdentity,
|
|
200
|
+
records,
|
|
201
|
+
rowHash: (row) => makeTryoutCatalogRecord(row).rowHash,
|
|
202
|
+
});
|
|
203
|
+
});
|