@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,123 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { verifySignedContentArtifact } from "#contracts/artifact/verify";
|
|
3
|
+
import { hashContentProjection } from "#contracts/projection/hash";
|
|
4
|
+
import { verifyContentReleaseBundle } from "#contracts/release/verify";
|
|
5
|
+
import { verifyContentRendererCompatibility } from "#contracts/renderer/compatibility";
|
|
6
|
+
import { validateLiveRendererManifestHash } from "#contracts/renderer/manifest";
|
|
7
|
+
import { ContentRuntimeMismatchError } from "#contracts/runtime/error";
|
|
8
|
+
import { decodePublicContentRuntimeRequest, decodePublicContentRuntimeResponse, } from "#contracts/runtime/spec";
|
|
9
|
+
/** Checks one article path preserves its pair-grouped physical source identity. */
|
|
10
|
+
function hasArticleSourcePath(projection, sourcePath) {
|
|
11
|
+
const prefix = `packages/corpus/articles/${projection.category}/`;
|
|
12
|
+
const suffix = `/${projection.artifactLocale}.mdx`;
|
|
13
|
+
if (!(sourcePath.startsWith(prefix) && sourcePath.endsWith(suffix))) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const sourceRoot = sourcePath.slice(prefix.length, -suffix.length);
|
|
17
|
+
const segments = sourceRoot.split("/");
|
|
18
|
+
return segments.length === 2 && segments.join("-") === projection.articleSlug;
|
|
19
|
+
}
|
|
20
|
+
/** Checks one page path matches its signed source-owned provenance. */
|
|
21
|
+
function hasPageSourcePath(projection, sourcePath) {
|
|
22
|
+
return sourcePath === projection.sourcePath;
|
|
23
|
+
}
|
|
24
|
+
/** Checks one public path exactly matches its projected content family. */
|
|
25
|
+
function hasProjectionSourcePath(projection, sourcePath) {
|
|
26
|
+
if (projection.kind === "article") {
|
|
27
|
+
return hasArticleSourcePath(projection, sourcePath);
|
|
28
|
+
}
|
|
29
|
+
if (projection.kind === "public-page") {
|
|
30
|
+
return hasPageSourcePath(projection, sourcePath);
|
|
31
|
+
}
|
|
32
|
+
return (sourcePath ===
|
|
33
|
+
`packages/corpus/${projection.contentKey}/${projection.artifactLocale}.mdx`);
|
|
34
|
+
}
|
|
35
|
+
/** Verifies one active public artifact belongs to its exact route request. */
|
|
36
|
+
const verifyPublicIdentity = Effect.fn("AksaraContracts.verifyPublicIdentity")(function* (request, response) {
|
|
37
|
+
if (response.projection.appLocale !== request.appLocale) {
|
|
38
|
+
return yield* new ContentRuntimeMismatchError({ reason: "locale" });
|
|
39
|
+
}
|
|
40
|
+
if (response.projection.publicPath !== request.publicPath) {
|
|
41
|
+
return yield* new ContentRuntimeMismatchError({ reason: "publicPath" });
|
|
42
|
+
}
|
|
43
|
+
if (!hasProjectionSourcePath(response.projection, response.sourcePath)) {
|
|
44
|
+
return yield* new ContentRuntimeMismatchError({ reason: "sourcePath" });
|
|
45
|
+
}
|
|
46
|
+
if (response.projectionHash !== hashContentProjection(response.projection)) {
|
|
47
|
+
return yield* new ContentRuntimeMismatchError({
|
|
48
|
+
reason: "projectionHash",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
/** Binds public content to the exact authenticated active release. */
|
|
53
|
+
const verifyPublicRelease = Effect.fn("AksaraContracts.verifyPublicRelease")(function* (response, bundle) {
|
|
54
|
+
if (response.activeReleaseId !== bundle.release.manifest.releaseId) {
|
|
55
|
+
return yield* new ContentRuntimeMismatchError({
|
|
56
|
+
reason: "activeReleaseId",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (response.activeManifestHash !== bundle.release.manifestHash) {
|
|
60
|
+
return yield* new ContentRuntimeMismatchError({
|
|
61
|
+
reason: "activeManifestHash",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
/** Authenticates one public response and applies its explicit use policy. */
|
|
66
|
+
const verifyPublicRuntimeExchange = Effect.fn("AksaraContracts.verifyPublicRuntimeExchange")(function* (input) {
|
|
67
|
+
const request = yield* decodePublicContentRuntimeRequest(input.request);
|
|
68
|
+
const response = yield* decodePublicContentRuntimeResponse(input.response);
|
|
69
|
+
if (response.kind !== "found") {
|
|
70
|
+
return response;
|
|
71
|
+
}
|
|
72
|
+
yield* verifyPublicIdentity(request, response);
|
|
73
|
+
const bundle = yield* verifyContentReleaseBundle({
|
|
74
|
+
release: response.release,
|
|
75
|
+
rendererManifest: response.rendererManifest,
|
|
76
|
+
});
|
|
77
|
+
yield* verifyPublicRelease(response, bundle);
|
|
78
|
+
const artifact = yield* verifySignedContentArtifact({
|
|
79
|
+
artifact: response.artifact,
|
|
80
|
+
rendererContractVersion: bundle.release.manifest.rendererContractVersion,
|
|
81
|
+
rendererManifest: bundle.rendererManifest,
|
|
82
|
+
});
|
|
83
|
+
if (input.policy.kind === "evidence") {
|
|
84
|
+
return response;
|
|
85
|
+
}
|
|
86
|
+
const liveRenderer = yield* validateLiveRendererManifestHash(input.policy.rendererManifest);
|
|
87
|
+
if (liveRenderer.hash !== bundle.rendererManifest.hash) {
|
|
88
|
+
yield* verifyContentRendererCompatibility({
|
|
89
|
+
payload: artifact.payload,
|
|
90
|
+
rendererContractVersion: bundle.release.manifest.rendererContractVersion,
|
|
91
|
+
rendererManifest: liveRenderer,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return response;
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Authenticates independently signed runtime evidence for one exact request.
|
|
98
|
+
*
|
|
99
|
+
* This capability does not claim that a non-rendering consumer can execute the
|
|
100
|
+
* artifact with a live renderer.
|
|
101
|
+
*/
|
|
102
|
+
export const verifyContentRuntimeEvidenceExchange = Effect.fn("AksaraContracts.verifyContentRuntimeEvidenceExchange")(function* (input) {
|
|
103
|
+
return yield* verifyPublicRuntimeExchange({
|
|
104
|
+
...input,
|
|
105
|
+
policy: { kind: "evidence" },
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Verifies independently signed runtime values selected for one exact request.
|
|
110
|
+
*
|
|
111
|
+
* Active catalog membership is trusted authenticated target state as recorded
|
|
112
|
+
* in ADR 0002, not an artifact property inferred from the release digest.
|
|
113
|
+
*/
|
|
114
|
+
export const verifyContentRuntimeExchange = Effect.fn("AksaraContracts.verifyContentRuntimeExchange")(function* (input) {
|
|
115
|
+
return yield* verifyPublicRuntimeExchange({
|
|
116
|
+
policy: {
|
|
117
|
+
kind: "execution",
|
|
118
|
+
rendererManifest: input.rendererManifest,
|
|
119
|
+
},
|
|
120
|
+
request: input.request,
|
|
121
|
+
response: input.response,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Context, type Effect, Schema } from "effect";
|
|
2
|
+
import { type SigningKeyId } from "#contracts/ids";
|
|
3
|
+
/** Authenticated object whose Ed25519 signature is being checked. */
|
|
4
|
+
export declare const ContentSignatureSubjectSchema: Schema.Literals<readonly ["artifact", "release", "tryout-runtime-bundle"]>;
|
|
5
|
+
export type ContentSignatureSubject = typeof ContentSignatureSubjectSchema.Type;
|
|
6
|
+
declare const SigningKeyNotFoundError_base: Schema.Class<SigningKeyNotFoundError, Schema.TaggedStruct<"SigningKeyNotFoundError", {
|
|
7
|
+
readonly keyId: Schema.brand<Schema.String, "@NakafaAI/AksaraSigningKeyId">;
|
|
8
|
+
}>, import("effect/Cause").YieldableError>;
|
|
9
|
+
/** No trusted public key exists for the requested key identifier. */
|
|
10
|
+
export declare class SigningKeyNotFoundError extends SigningKeyNotFoundError_base {
|
|
11
|
+
}
|
|
12
|
+
declare const SigningKeyResolutionError_base: Schema.Class<SigningKeyResolutionError, Schema.TaggedStruct<"SigningKeyResolutionError", {
|
|
13
|
+
readonly keyId: Schema.brand<Schema.String, "@NakafaAI/AksaraSigningKeyId">;
|
|
14
|
+
}>, import("effect/Cause").YieldableError>;
|
|
15
|
+
/** The trusted key source could not resolve the requested public key. */
|
|
16
|
+
export declare class SigningKeyResolutionError extends SigningKeyResolutionError_base {
|
|
17
|
+
}
|
|
18
|
+
declare const PublicKeyParseError_base: Schema.Class<PublicKeyParseError, Schema.TaggedStruct<"PublicKeyParseError", {
|
|
19
|
+
readonly keyId: Schema.brand<Schema.String, "@NakafaAI/AksaraSigningKeyId">;
|
|
20
|
+
readonly subject: Schema.Literals<readonly ["artifact", "release", "tryout-runtime-bundle"]>;
|
|
21
|
+
}>, import("effect/Cause").YieldableError>;
|
|
22
|
+
/** A resolved public key could not be parsed without exposing its contents. */
|
|
23
|
+
export declare class PublicKeyParseError extends PublicKeyParseError_base {
|
|
24
|
+
}
|
|
25
|
+
declare const PublicKeyTypeError_base: Schema.Class<PublicKeyTypeError, Schema.TaggedStruct<"PublicKeyTypeError", {
|
|
26
|
+
readonly keyId: Schema.brand<Schema.String, "@NakafaAI/AksaraSigningKeyId">;
|
|
27
|
+
readonly subject: Schema.Literals<readonly ["artifact", "release", "tryout-runtime-bundle"]>;
|
|
28
|
+
}>, import("effect/Cause").YieldableError>;
|
|
29
|
+
/** A resolved public key is not an Ed25519 verification key. */
|
|
30
|
+
export declare class PublicKeyTypeError extends PublicKeyTypeError_base {
|
|
31
|
+
}
|
|
32
|
+
declare const SignatureCheckError_base: Schema.Class<SignatureCheckError, Schema.TaggedStruct<"SignatureCheckError", {
|
|
33
|
+
readonly keyId: Schema.brand<Schema.String, "@NakafaAI/AksaraSigningKeyId">;
|
|
34
|
+
readonly subject: Schema.Literals<readonly ["artifact", "release", "tryout-runtime-bundle"]>;
|
|
35
|
+
}>, import("effect/Cause").YieldableError>;
|
|
36
|
+
/** Web Crypto could not complete an Ed25519 verification operation. */
|
|
37
|
+
export declare class SignatureCheckError extends SignatureCheckError_base {
|
|
38
|
+
}
|
|
39
|
+
declare const SignatureInvalidError_base: Schema.Class<SignatureInvalidError, Schema.TaggedStruct<"SignatureInvalidError", {
|
|
40
|
+
readonly keyId: Schema.brand<Schema.String, "@NakafaAI/AksaraSigningKeyId">;
|
|
41
|
+
readonly subject: Schema.Literals<readonly ["artifact", "release", "tryout-runtime-bundle"]>;
|
|
42
|
+
}>, import("effect/Cause").YieldableError>;
|
|
43
|
+
/** The signature does not authenticate the supplied canonical bytes. */
|
|
44
|
+
export declare class SignatureInvalidError extends SignatureInvalidError_base {
|
|
45
|
+
}
|
|
46
|
+
declare const ContentVerificationKeyResolver_base: Context.ServiceClass<ContentVerificationKeyResolver, "AksaraContentVerificationKeyResolver", {
|
|
47
|
+
/** Resolves a reviewed public key without exposing key material in errors. */
|
|
48
|
+
readonly resolve: (keyId: SigningKeyId) => Effect.Effect<string, SigningKeyNotFoundError | SigningKeyResolutionError>;
|
|
49
|
+
}>;
|
|
50
|
+
/** Server-side trust seam for resolving reviewed Ed25519 public keys. */
|
|
51
|
+
export declare class ContentVerificationKeyResolver extends ContentVerificationKeyResolver_base {
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Context, Schema } from "effect";
|
|
2
|
+
import { SigningKeyIdSchema } from "#contracts/ids";
|
|
3
|
+
/** Authenticated object whose Ed25519 signature is being checked. */
|
|
4
|
+
export const ContentSignatureSubjectSchema = Schema.Literals([
|
|
5
|
+
"artifact",
|
|
6
|
+
"release",
|
|
7
|
+
"tryout-runtime-bundle",
|
|
8
|
+
]);
|
|
9
|
+
/** No trusted public key exists for the requested key identifier. */
|
|
10
|
+
export class SigningKeyNotFoundError extends Schema.TaggedError()("SigningKeyNotFoundError", { keyId: SigningKeyIdSchema }) {
|
|
11
|
+
}
|
|
12
|
+
/** The trusted key source could not resolve the requested public key. */
|
|
13
|
+
export class SigningKeyResolutionError extends Schema.TaggedError()("SigningKeyResolutionError", { keyId: SigningKeyIdSchema }) {
|
|
14
|
+
}
|
|
15
|
+
/** A resolved public key could not be parsed without exposing its contents. */
|
|
16
|
+
export class PublicKeyParseError extends Schema.TaggedError()("PublicKeyParseError", { keyId: SigningKeyIdSchema, subject: ContentSignatureSubjectSchema }) {
|
|
17
|
+
}
|
|
18
|
+
/** A resolved public key is not an Ed25519 verification key. */
|
|
19
|
+
export class PublicKeyTypeError extends Schema.TaggedError()("PublicKeyTypeError", { keyId: SigningKeyIdSchema, subject: ContentSignatureSubjectSchema }) {
|
|
20
|
+
}
|
|
21
|
+
/** Web Crypto could not complete an Ed25519 verification operation. */
|
|
22
|
+
export class SignatureCheckError extends Schema.TaggedError()("SignatureCheckError", { keyId: SigningKeyIdSchema, subject: ContentSignatureSubjectSchema }) {
|
|
23
|
+
}
|
|
24
|
+
/** The signature does not authenticate the supplied canonical bytes. */
|
|
25
|
+
export class SignatureInvalidError extends Schema.TaggedError()("SignatureInvalidError", { keyId: SigningKeyIdSchema, subject: ContentSignatureSubjectSchema }) {
|
|
26
|
+
}
|
|
27
|
+
/** Server-side trust seam for resolving reviewed Ed25519 public keys. */
|
|
28
|
+
export class ContentVerificationKeyResolver extends Context.Service()("AksaraContentVerificationKeyResolver") {
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { SigningKeyNotFoundError, SigningKeyResolutionError } from "#contracts/signature/spec";
|
|
3
|
+
/** One code-reviewed Ed25519 public key retained for release verification. */
|
|
4
|
+
export declare const TrustedKeySchema: Schema.Struct<{
|
|
5
|
+
readonly keyId: Schema.brand<Schema.String, "@NakafaAI/AksaraSigningKeyId">;
|
|
6
|
+
readonly publicKeyPem: Schema.NonEmptyString;
|
|
7
|
+
}>;
|
|
8
|
+
export type TrustedKey = typeof TrustedKeySchema.Type;
|
|
9
|
+
/** Only this reviewed identity may sign newly published content. */
|
|
10
|
+
export declare const ACTIVE_SIGNING_KEY_ID: string & import("effect/Brand").Brand<"@NakafaAI/AksaraSigningKeyId">;
|
|
11
|
+
/** Production keys retained while signed content may still reference them. */
|
|
12
|
+
export declare const TRUSTED_CONTENT_KEYS: readonly Readonly<{
|
|
13
|
+
readonly keyId: string & import("effect/Brand").Brand<"@NakafaAI/AksaraSigningKeyId">;
|
|
14
|
+
readonly publicKeyPem: string;
|
|
15
|
+
}>[];
|
|
16
|
+
/** Builds an immutable exact-ID resolver from code-owned retained entries. */
|
|
17
|
+
export declare function makeTrustedKeyResolver(entries: readonly TrustedKey[]): {
|
|
18
|
+
readonly resolve: (keyId: import("#contracts/ids").SigningKeyId) => Effect.Effect<string, SigningKeyNotFoundError | SigningKeyResolutionError>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { SigningKeyIdSchema } from "#contracts/ids";
|
|
3
|
+
import { ContentVerificationKeyResolver, SigningKeyNotFoundError, SigningKeyResolutionError, } from "#contracts/signature/spec";
|
|
4
|
+
/** One code-reviewed Ed25519 public key retained for release verification. */
|
|
5
|
+
export const TrustedKeySchema = Schema.Struct({
|
|
6
|
+
keyId: SigningKeyIdSchema,
|
|
7
|
+
publicKeyPem: Schema.NonEmptyString,
|
|
8
|
+
});
|
|
9
|
+
const ACTIVE_CONTENT_KEY = Object.freeze(TrustedKeySchema.make({
|
|
10
|
+
keyId: SigningKeyIdSchema.make("content-2026-07-23"),
|
|
11
|
+
publicKeyPem: "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEADaLoLeK2jGt3Jav3xpfXU5BNWYOo086miCmkV8FCmsE=\n-----END PUBLIC KEY-----\n",
|
|
12
|
+
}));
|
|
13
|
+
/** Only this reviewed identity may sign newly published content. */
|
|
14
|
+
export const ACTIVE_SIGNING_KEY_ID = ACTIVE_CONTENT_KEY.keyId;
|
|
15
|
+
/** Production keys retained while signed content may still reference them. */
|
|
16
|
+
export const TRUSTED_CONTENT_KEYS = Object.freeze([ACTIVE_CONTENT_KEY]);
|
|
17
|
+
/** Builds an immutable exact-ID resolver from code-owned retained entries. */
|
|
18
|
+
export function makeTrustedKeyResolver(entries) {
|
|
19
|
+
const retained = entries.map((entry) => ({ ...entry }));
|
|
20
|
+
return ContentVerificationKeyResolver.of({
|
|
21
|
+
resolve: (keyId) => {
|
|
22
|
+
const [match, ...duplicates] = retained.filter((entry) => entry.keyId === keyId);
|
|
23
|
+
if (match === undefined) {
|
|
24
|
+
return Effect.fail(new SigningKeyNotFoundError({ keyId }));
|
|
25
|
+
}
|
|
26
|
+
if (duplicates.length > 0) {
|
|
27
|
+
return Effect.fail(new SigningKeyResolutionError({ keyId }));
|
|
28
|
+
}
|
|
29
|
+
return Effect.succeed(match.publicKeyPem);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { Ed25519Signature, SigningKeyId } from "#contracts/ids";
|
|
3
|
+
import { type ContentSignatureSubject, ContentVerificationKeyResolver, PublicKeyParseError, PublicKeyTypeError, SignatureCheckError, SignatureInvalidError } from "#contracts/signature/spec";
|
|
4
|
+
/** Resolves trust and verifies domain-separated canonical bytes with Ed25519. */
|
|
5
|
+
export declare const verifyEd25519Signature: (input: {
|
|
6
|
+
readonly keyId: SigningKeyId;
|
|
7
|
+
readonly message: string;
|
|
8
|
+
readonly signature: Ed25519Signature;
|
|
9
|
+
readonly subject: ContentSignatureSubject;
|
|
10
|
+
}) => Effect.Effect<undefined, PublicKeyParseError | PublicKeyTypeError | SignatureCheckError | SignatureInvalidError | import("#contracts/signature/spec").SigningKeyNotFoundError | import("#contracts/signature/spec").SigningKeyResolutionError, ContentVerificationKeyResolver>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { ContentVerificationKeyResolver, PublicKeyParseError, PublicKeyTypeError, SignatureCheckError, SignatureInvalidError, } from "#contracts/signature/spec";
|
|
3
|
+
const PUBLIC_KEY_HEADER = "-----BEGIN PUBLIC KEY-----\n";
|
|
4
|
+
const PUBLIC_KEY_FOOTER = "-----END PUBLIC KEY-----";
|
|
5
|
+
const ED25519_SPKI_PREFIX = "302a300506032b6570032100";
|
|
6
|
+
const PUBLIC_KEY_BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u;
|
|
7
|
+
/** Decodes canonical base64 text without a Node Buffer dependency. */
|
|
8
|
+
function decodeBase64(value) {
|
|
9
|
+
const decoded = atob(value);
|
|
10
|
+
return Uint8Array.from(decoded, (character) => character.charCodeAt(0));
|
|
11
|
+
}
|
|
12
|
+
/** Encodes bytes as lowercase hexadecimal for one exact key-type check. */
|
|
13
|
+
function encodeHex(value) {
|
|
14
|
+
let hex = "";
|
|
15
|
+
for (const byte of value) {
|
|
16
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
17
|
+
}
|
|
18
|
+
return hex;
|
|
19
|
+
}
|
|
20
|
+
/** Reads the reviewed SPKI bytes from one exact public-key PEM. */
|
|
21
|
+
function decodePublicKeyPem(keyId, publicKeyPem, subject) {
|
|
22
|
+
const normalizedPublicKeyPem = publicKeyPem.replaceAll("\r\n", "\n");
|
|
23
|
+
if (!(normalizedPublicKeyPem.startsWith(PUBLIC_KEY_HEADER) &&
|
|
24
|
+
normalizedPublicKeyPem.trimEnd().endsWith(PUBLIC_KEY_FOOTER))) {
|
|
25
|
+
return Effect.fail(new PublicKeyParseError({ keyId, subject }));
|
|
26
|
+
}
|
|
27
|
+
return Effect.try({
|
|
28
|
+
catch: () => new PublicKeyParseError({ keyId, subject }),
|
|
29
|
+
try: () => {
|
|
30
|
+
const body = normalizedPublicKeyPem
|
|
31
|
+
.slice(PUBLIC_KEY_HEADER.length)
|
|
32
|
+
.replace(PUBLIC_KEY_FOOTER, "")
|
|
33
|
+
.replaceAll("\n", "")
|
|
34
|
+
.trim();
|
|
35
|
+
if (!PUBLIC_KEY_BASE64_PATTERN.test(body) || body.length % 4 !== 0) {
|
|
36
|
+
throw new TypeError("Invalid public-key base64");
|
|
37
|
+
}
|
|
38
|
+
return decodeBase64(body);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Imports one exact Ed25519 SPKI key through Web Crypto. */
|
|
43
|
+
function importEd25519PublicKey(keyId, publicKeyPem, subject) {
|
|
44
|
+
return Effect.gen(function* () {
|
|
45
|
+
const bytes = yield* decodePublicKeyPem(keyId, publicKeyPem, subject);
|
|
46
|
+
if (bytes.byteLength !== 44 ||
|
|
47
|
+
!encodeHex(bytes.slice(0, 12)).startsWith(ED25519_SPKI_PREFIX)) {
|
|
48
|
+
return yield* new PublicKeyTypeError({ keyId, subject });
|
|
49
|
+
}
|
|
50
|
+
return yield* Effect.tryPromise({
|
|
51
|
+
catch: () => new PublicKeyParseError({ keyId, subject }),
|
|
52
|
+
try: () => crypto.subtle.importKey("spki", bytes, { name: "Ed25519" }, false, [
|
|
53
|
+
"verify",
|
|
54
|
+
]),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Decodes one unpadded base64url Ed25519 signature. */
|
|
59
|
+
function decodeSignature(signature) {
|
|
60
|
+
const base64 = signature.replaceAll("-", "+").replaceAll("_", "/");
|
|
61
|
+
return decodeBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
|
|
62
|
+
}
|
|
63
|
+
/** Resolves trust and verifies domain-separated canonical bytes with Ed25519. */
|
|
64
|
+
export const verifyEd25519Signature = Effect.fn("AksaraContracts.verifyEd25519Signature")(function* (input) {
|
|
65
|
+
const resolver = yield* ContentVerificationKeyResolver;
|
|
66
|
+
const publicKeyPem = yield* resolver.resolve(input.keyId);
|
|
67
|
+
const publicKey = yield* importEd25519PublicKey(input.keyId, publicKeyPem, input.subject);
|
|
68
|
+
const valid = yield* Effect.tryPromise({
|
|
69
|
+
catch: () => new SignatureCheckError({
|
|
70
|
+
keyId: input.keyId,
|
|
71
|
+
subject: input.subject,
|
|
72
|
+
}),
|
|
73
|
+
try: () => crypto.subtle.verify("Ed25519", publicKey, decodeSignature(input.signature), new TextEncoder().encode(input.message)),
|
|
74
|
+
});
|
|
75
|
+
if (valid) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
return yield* new SignatureInvalidError({
|
|
79
|
+
keyId: input.keyId,
|
|
80
|
+
subject: input.subject,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Checks one lowercase kebab-case segment without owning its domain meaning. */
|
|
2
|
+
export declare function isLowerKebab(value: string): boolean;
|
|
3
|
+
/** Checks one slash-separated path of lowercase kebab-case segments. */
|
|
4
|
+
export declare function isLowerKebabPath(value: string): boolean;
|
|
5
|
+
/** Checks one whitespace-free absolute HTTPS source URL. */
|
|
6
|
+
export declare function isHttpsUrl(value: string): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const LOWER_KEBAB_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
2
|
+
const LOWER_KEBAB_PATH_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/u;
|
|
3
|
+
const HTTPS_URL_PATTERN = /^https:\/\/\S+$/u;
|
|
4
|
+
/** Checks one lowercase kebab-case segment without owning its domain meaning. */
|
|
5
|
+
export function isLowerKebab(value) {
|
|
6
|
+
return LOWER_KEBAB_PATTERN.test(value);
|
|
7
|
+
}
|
|
8
|
+
/** Checks one slash-separated path of lowercase kebab-case segments. */
|
|
9
|
+
export function isLowerKebabPath(value) {
|
|
10
|
+
return LOWER_KEBAB_PATH_PATTERN.test(value);
|
|
11
|
+
}
|
|
12
|
+
/** Checks one whitespace-free absolute HTTPS source URL. */
|
|
13
|
+
export function isHttpsUrl(value) {
|
|
14
|
+
return HTTPS_URL_PATTERN.test(value);
|
|
15
|
+
}
|