@agentxm/extension-content 0.38.0 → 0.39.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.
Files changed (34) hide show
  1. package/dist/src/content/frontmatter.js +9 -22
  2. package/dist/src/content/skill-content.d.ts +14 -0
  3. package/dist/src/content/skill-content.js +24 -0
  4. package/dist/src/content/subagent-content.d.ts +70 -46
  5. package/dist/src/content/subagent-content.js +273 -85
  6. package/dist/src/index.d.ts +4 -4
  7. package/dist/src/index.js +4 -4
  8. package/dist/src/knowledge/package-inspection.d.ts +7 -7
  9. package/dist/src/lint/catalog/hook/entrypoint-exists.js +18 -15
  10. package/dist/src/lint/catalog/hook/envelope.d.ts +14 -1
  11. package/dist/src/lint/catalog/hook/envelope.js +25 -2
  12. package/dist/src/lint/catalog/hook/native-bindings-supported.d.ts +4 -0
  13. package/dist/src/lint/catalog/hook/native-bindings-supported.js +39 -0
  14. package/dist/src/lint/catalog/hook.js +2 -4
  15. package/dist/src/lint/catalog/skill-accessor/contexts.d.ts +1 -0
  16. package/dist/src/lint/catalog/skill-accessor/contexts.js +3 -0
  17. package/dist/src/lint/catalog/skill.d.ts +1 -1
  18. package/dist/src/lint/catalog/skill.js +20 -8
  19. package/dist/src/lint/catalog/subagent/envelope.js +1 -1
  20. package/dist/src/lint/catalog-metadata.js +3 -9
  21. package/dist/src/lint/context.d.ts +2 -0
  22. package/dist/src/packaging/archive-guardrails.d.ts +1 -0
  23. package/dist/src/packaging/archive-guardrails.js +205 -11
  24. package/dist/src/packaging/filtered-package-validation.js +24 -31
  25. package/dist/src/packaging/input-normalization.d.ts +0 -1
  26. package/dist/src/packaging/input-normalization.js +1 -77
  27. package/dist/src/packaging/manifest-file.js +1 -1
  28. package/dist/src/packaging/manifest-policy.d.ts +239 -109
  29. package/dist/src/packaging/manifest-policy.js +1 -1
  30. package/package.json +4 -2
  31. package/dist/src/lint/catalog/hook/decision-portability.d.ts +0 -4
  32. package/dist/src/lint/catalog/hook/decision-portability.js +0 -40
  33. package/dist/src/lint/catalog/hook/matcher-raw-portability.d.ts +0 -4
  34. package/dist/src/lint/catalog/hook/matcher-raw-portability.js +0 -51
@@ -1,3 +1,4 @@
1
+ import { inflateRawSync } from "node:zlib";
1
2
  import * as Data from "effect/Data";
2
3
  import * as Effect from "effect/Effect";
3
4
  export class ArchiveGuardrailError extends Data.TaggedError("ArchiveGuardrailError") {
@@ -8,6 +9,8 @@ export const ZIP_LOCAL_SIGNATURE = 0x04034b50;
8
9
  const COMPRESSION_STORE = 0;
9
10
  const COMPRESSION_DEFLATE = 8;
10
11
  const SUPPORTED_COMPRESSION = new Set([COMPRESSION_STORE, COMPRESSION_DEFLATE]);
12
+ // Bound target decoding and segment allocation separately from ordinary payload files.
13
+ const MAX_LINK_TARGET_BYTES = 64 * 1024;
11
14
  const S_IFLNK = 0xa000;
12
15
  const S_IFMT = 0xf000;
13
16
  const textDecoder = new TextDecoder();
@@ -98,10 +101,92 @@ export const parseZipCentralDirectory = (buf) => Effect.gen(function* () {
98
101
  }
99
102
  return entries;
100
103
  });
104
+ const LOCAL_FILE_HEADER_SIZE = 30;
105
+ const INFLATE_CHUNK_SIZE = 16 * 1024;
106
+ export const defaultReadEntry = (archiveBytes, entry) => Effect.gen(function* () {
107
+ const { localHeaderOffset, compressedSize, compressionMethod, fileName } = entry;
108
+ if (localHeaderOffset + LOCAL_FILE_HEADER_SIZE > archiveBytes.length) {
109
+ return yield* new ArchiveGuardrailError({
110
+ code: "malformed_archive",
111
+ message: `Local file header for entry "${fileName}" exceeds archive bounds.`,
112
+ entry: fileName,
113
+ });
114
+ }
115
+ const view = new DataView(archiveBytes.buffer, archiveBytes.byteOffset, archiveBytes.byteLength);
116
+ const signature = view.getUint32(localHeaderOffset, true);
117
+ if (signature !== ZIP_LOCAL_SIGNATURE) {
118
+ return yield* new ArchiveGuardrailError({
119
+ code: "malformed_archive",
120
+ message: `Invalid local file header signature for entry "${fileName}".`,
121
+ entry: fileName,
122
+ });
123
+ }
124
+ const fileNameLength = view.getUint16(localHeaderOffset + 26, true);
125
+ const extraFieldLength = view.getUint16(localHeaderOffset + 28, true);
126
+ const dataStart = localHeaderOffset + LOCAL_FILE_HEADER_SIZE + fileNameLength + extraFieldLength;
127
+ if (dataStart + compressedSize > archiveBytes.length) {
128
+ return yield* new ArchiveGuardrailError({
129
+ code: "malformed_archive",
130
+ message: `Compressed data for entry "${fileName}" exceeds archive bounds.`,
131
+ entry: fileName,
132
+ });
133
+ }
134
+ const compressedData = archiveBytes.slice(dataStart, dataStart + compressedSize);
135
+ if (compressionMethod === 0) {
136
+ if (compressedData.byteLength !== entry.uncompressedSize) {
137
+ return yield* new ArchiveGuardrailError({
138
+ code: "malformed_archive",
139
+ message: `Stored entry size does not match its declaration: "${fileName}".`,
140
+ entry: fileName,
141
+ });
142
+ }
143
+ return compressedData;
144
+ }
145
+ if (compressionMethod === 8) {
146
+ const expansionFailure = () => new ArchiveGuardrailError({
147
+ code: "decompression_limit_exceeded",
148
+ message: `Entry "${fileName}" decompresses beyond its declared size of ${entry.uncompressedSize} bytes.`,
149
+ entry: fileName,
150
+ });
151
+ // workerd grows its bounded zlib buffer by whole chunks, including the
152
+ // final partial chunk. Reserve one chunk of codec headroom, then enforce
153
+ // the exact declared size below. Expansion remains bounded during decode.
154
+ const result = yield* Effect.try({
155
+ try: () => inflateRawSync(compressedData, {
156
+ chunkSize: INFLATE_CHUNK_SIZE,
157
+ maxOutputLength: entry.uncompressedSize + INFLATE_CHUNK_SIZE,
158
+ }),
159
+ catch: (error) => {
160
+ const errorCode = typeof error === "object" &&
161
+ error !== null &&
162
+ "code" in error &&
163
+ typeof error.code === "string"
164
+ ? error.code
165
+ : "";
166
+ return errorCode === "ERR_BUFFER_TOO_LARGE" ||
167
+ (error instanceof RangeError && error.message === "Memory limit exceeded")
168
+ ? expansionFailure()
169
+ : new ArchiveGuardrailError({
170
+ code: "malformed_archive",
171
+ message: `Failed to decompress entry "${fileName}".`,
172
+ entry: fileName,
173
+ });
174
+ },
175
+ });
176
+ if (result.byteLength > entry.uncompressedSize)
177
+ return yield* expansionFailure();
178
+ return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
179
+ }
180
+ return yield* new ArchiveGuardrailError({
181
+ code: "unsupported_compression",
182
+ message: `Unsupported compression method ${compressionMethod} for entry "${fileName}".`,
183
+ entry: fileName,
184
+ });
185
+ });
101
186
  const checkPathTraversal = (entries) => {
102
187
  for (const entry of entries) {
103
188
  const normalized = entry.fileName.replace(/\\/g, "/");
104
- if (normalized.includes("../") || normalized.includes("/..") || normalized === "..") {
189
+ if (normalized.split("/").includes("..")) {
105
190
  return Effect.fail(new ArchiveGuardrailError({
106
191
  code: "path_traversal",
107
192
  message: `Archive entry contains path traversal: "${entry.fileName}".`,
@@ -127,7 +212,7 @@ const checkAbsolutePaths = (entries) => {
127
212
  const checkDuplicateEntries = (entries) => {
128
213
  const seen = new Set();
129
214
  for (const entry of entries) {
130
- const normalized = entry.fileName.toLowerCase();
215
+ const normalized = entry.fileName.replace(/\/$/, "").normalize("NFC").toLowerCase();
131
216
  if (seen.has(normalized)) {
132
217
  return Effect.fail(new ArchiveGuardrailError({
133
218
  code: "duplicate_entry",
@@ -139,19 +224,127 @@ const checkDuplicateEntries = (entries) => {
139
224
  }
140
225
  return Effect.void;
141
226
  };
142
- const checkSymlinks = (entries) => {
227
+ // Normalize only for portable identity comparisons; preserve original archive bytes.
228
+ const memberKey = (name) => name.normalize("NFC").toLowerCase();
229
+ const checkMemberTopology = (entries) => Effect.gen(function* () {
230
+ const members = new Map();
143
231
  for (const entry of entries) {
144
- const unixMode = entry.externalAttributes >>> 16;
145
- if ((unixMode & S_IFMT) === S_IFLNK) {
146
- return Effect.fail(new ArchiveGuardrailError({
232
+ const name = entry.fileName.replace(/\/$/, "");
233
+ const kind = (entry.externalAttributes >>> 16) & S_IFMT;
234
+ if (name.length === 0 ||
235
+ /[\\\0:]/.test(name) ||
236
+ name.split("/").some((part) => part === "" || part === "." || part === "..") ||
237
+ ![0, 0x8000, 0x4000, S_IFLNK].includes(kind) ||
238
+ (entry.fileName.endsWith("/") && kind !== 0 && kind !== 0x4000) ||
239
+ (kind === 0x4000 && !entry.fileName.endsWith("/"))) {
240
+ return yield* new ArchiveGuardrailError({
241
+ code: "malformed_archive",
242
+ message: `Archive entry has an ambiguous path or unsupported file kind: "${entry.fileName}".`,
243
+ entry: entry.fileName,
244
+ });
245
+ }
246
+ members.set(memberKey(name), entry);
247
+ }
248
+ for (const entry of entries) {
249
+ const parts = entry.fileName.replace(/\/$/, "").split("/");
250
+ parts.pop();
251
+ while (parts.length > 0) {
252
+ const ancestor = members.get(memberKey(parts.join("/")));
253
+ if (ancestor !== undefined &&
254
+ !ancestor.fileName.endsWith("/") &&
255
+ ((ancestor.externalAttributes >>> 16) & S_IFMT) !== 0x4000) {
256
+ return yield* new ArchiveGuardrailError({
257
+ code: "malformed_archive",
258
+ message: `Archive entry is nested beneath a file or link: "${entry.fileName}".`,
259
+ entry: entry.fileName,
260
+ });
261
+ }
262
+ parts.pop();
263
+ }
264
+ }
265
+ });
266
+ const checkSymlinks = (archiveBytes, entries) => Effect.gen(function* () {
267
+ const targets = new Map();
268
+ for (const entry of entries) {
269
+ if (((entry.externalAttributes >>> 16) & S_IFMT) !== S_IFLNK)
270
+ continue;
271
+ if (entry.uncompressedSize > MAX_LINK_TARGET_BYTES) {
272
+ return yield* new ArchiveGuardrailError({
147
273
  code: "symlink_entry",
148
- message: `Archive contains symlink entry: "${entry.fileName}".`,
149
274
  entry: entry.fileName,
150
- }));
275
+ message: `Archive link target exceeds the ${MAX_LINK_TARGET_BYTES}-byte validation limit: "${entry.fileName}".`,
276
+ });
151
277
  }
278
+ const bytes = yield* defaultReadEntry(archiveBytes, entry);
279
+ const target = yield* Effect.try({
280
+ try: () => new TextDecoder("utf-8", { fatal: true }).decode(bytes),
281
+ catch: () => new ArchiveGuardrailError({
282
+ code: "symlink_entry",
283
+ message: `Archive link target is not UTF-8: "${entry.fileName}".`,
284
+ entry: entry.fileName,
285
+ }),
286
+ });
287
+ if (target.length === 0 || target.startsWith("/") || /[\\\0:]/.test(target)) {
288
+ return yield* new ArchiveGuardrailError({
289
+ code: "symlink_entry",
290
+ message: `Archive link target must be a relative contained path: "${entry.fileName}".`,
291
+ entry: entry.fileName,
292
+ });
293
+ }
294
+ targets.set(memberKey(entry.fileName), target);
152
295
  }
153
- return Effect.void;
154
- };
296
+ // Expand links before processing subsequent '..' segments, as a filesystem
297
+ // does. Exit markers distinguish a cycle from a repeated finite traversal.
298
+ // Bound total interpretation work independently of decompressed byte limits.
299
+ let remainingSteps = 1_000_000;
300
+ for (const [name, target] of targets) {
301
+ const resolved = name.split("/").slice(0, -1);
302
+ const pending = target.split("/").reverse();
303
+ const active = new Set([name]);
304
+ while (pending.length > 0) {
305
+ if (--remainingSteps < 0) {
306
+ return yield* new ArchiveGuardrailError({
307
+ code: "symlink_entry",
308
+ message: "Archive link resolution exceeds the bounded validation budget.",
309
+ entry: name,
310
+ });
311
+ }
312
+ const part = pending.pop();
313
+ if (part === undefined)
314
+ break;
315
+ if (typeof part !== "string") {
316
+ active.delete(part.leave);
317
+ continue;
318
+ }
319
+ if (part === "" || part === ".")
320
+ continue;
321
+ if (part === "..") {
322
+ if (resolved.length === 0) {
323
+ return yield* new ArchiveGuardrailError({
324
+ code: "symlink_entry",
325
+ message: `Archive link escapes its package: "${name}".`,
326
+ entry: name,
327
+ });
328
+ }
329
+ resolved.pop();
330
+ continue;
331
+ }
332
+ const next = memberKey([...resolved, part].join("/"));
333
+ const nested = targets.get(next);
334
+ if (nested === undefined) {
335
+ resolved.push(part);
336
+ continue;
337
+ }
338
+ // A cycle remains inert payload; a filesystem cannot traverse beyond it.
339
+ if (active.has(next))
340
+ break;
341
+ active.add(next);
342
+ pending.push({ leave: next });
343
+ for (const segment of nested.split("/").reverse())
344
+ pending.push(segment);
345
+ }
346
+ }
347
+ });
155
348
  const checkCompressionMethods = (entries) => {
156
349
  for (const entry of entries) {
157
350
  if (!SUPPORTED_COMPRESSION.has(entry.compressionMethod)) {
@@ -240,11 +433,12 @@ export const validateArchive = (archiveBytes, limits) => Effect.gen(function* ()
240
433
  yield* checkPathTraversal(entries);
241
434
  yield* checkAbsolutePaths(entries);
242
435
  yield* checkDuplicateEntries(entries);
243
- yield* checkSymlinks(entries);
244
436
  yield* checkCompressionMethods(entries);
245
437
  yield* checkEntryCount(entries, appliedLimits);
246
438
  yield* checkDecompressedSize(entries, appliedLimits);
247
439
  yield* checkCompressionRatio(entries, appliedLimits);
440
+ yield* checkMemberTopology(entries);
441
+ yield* checkSymlinks(archiveBytes, entries);
248
442
  return entries;
249
443
  });
250
444
  //# sourceMappingURL=archive-guardrails.js.map
@@ -1,20 +1,16 @@
1
1
  import * as Data from "effect/Data";
2
2
  import * as Effect from "effect/Effect";
3
- import * as Option from "effect/Option";
3
+ import * as Schema from "effect/Schema";
4
+ import { HookManifestSchema, requiredHookRuntimeFiles, } from "@agentxm/extension-model/unstable/hooks/manifest-schema";
4
5
  import { parseFrontmatterEffect } from "../content/frontmatter.js";
5
6
  import { inspectKnowledgeEntries } from "../knowledge/okf.js";
6
- import { parseSkillMd } from "../content/skill-content.js";
7
- import { parseSubagentMd } from "../content/subagent-content.js";
7
+ import { readSubagentPackage } from "../content/subagent-content.js";
8
8
  /** A type-specific package invariant failed against the filtered archive. */
9
9
  export class FilteredPackageError extends Data.TaggedError("FilteredPackageError") {
10
10
  }
11
11
  const rawField = (raw, field) => typeof raw === "object" && raw !== null && !Array.isArray(raw)
12
12
  ? Reflect.get(raw, field)
13
13
  : undefined;
14
- const safeArchivePath = (value) => value.length > 0 &&
15
- !value.startsWith("/") &&
16
- !value.includes("\\") &&
17
- !value.split("/").some((segment) => segment === "" || segment === "." || segment === "..");
18
14
  const requireEntry = (entries, path) => {
19
15
  const found = entries.find((entry) => entry.fileName === path);
20
16
  return found === undefined
@@ -36,28 +32,25 @@ const readText = (args, path) => Effect.gen(function* () {
36
32
  });
37
33
  /** Validate the complete type-specific package after Registry-only filtering. */
38
34
  export const validateFilteredPackage = (args) => Effect.gen(function* () {
39
- const name = args.manifest.identity.name;
40
35
  switch (args.type) {
41
36
  case "skill": {
42
37
  const path = "src/SKILL.md";
43
- const content = yield* readText(args, path);
44
- if (Option.isNone(parseSkillMd(content, name))) {
45
- return yield* new FilteredPackageError({
46
- code: "content_invalid",
47
- detail: `Filtered "${path}" must contain valid Agent Skill frontmatter whose name is "${name}".`,
48
- path,
49
- });
50
- }
38
+ // The envelope owns Registry identity. Authoring lint owns the chosen
39
+ // frontmatter convention; ingest must not rewrite or reject upstream metadata.
40
+ yield* readText(args, path);
51
41
  return;
52
42
  }
53
43
  case "subagent": {
54
- const path = `src/${name}.md`;
55
- const content = yield* readText(args, path);
56
- yield* parseSubagentMd(content, name).pipe(Effect.mapError(() => new FilteredPackageError({
57
- code: "content_invalid",
58
- detail: `Filtered "${path}" must contain valid subagent frontmatter whose name is "${name}".`,
59
- path,
60
- })));
44
+ yield* readSubagentPackage({
45
+ manifest: args.manifest.raw,
46
+ readFile: (path) => readText(args, path),
47
+ }).pipe(Effect.mapError((error) => error instanceof FilteredPackageError
48
+ ? error
49
+ : new FilteredPackageError({
50
+ code: error.reason === "reference-invalid" ? "reference_invalid" : "content_invalid",
51
+ detail: error.detail,
52
+ ...(error.source === undefined ? {} : { path: error.source }),
53
+ })));
61
54
  return;
62
55
  }
63
56
  case "rule": {
@@ -71,14 +64,14 @@ export const validateFilteredPackage = (args) => Effect.gen(function* () {
71
64
  return;
72
65
  }
73
66
  case "hook": {
74
- const entrypoint = rawField(args.manifest.raw, "entrypoint");
75
- if (typeof entrypoint !== "string" || !safeArchivePath(entrypoint)) {
76
- return yield* new FilteredPackageError({
77
- code: "reference_invalid",
78
- detail: "Hook entrypoint must be a safe package-relative file path.",
79
- });
80
- }
81
- yield* requireEntry(args.entries, entrypoint);
67
+ const manifest = yield* Schema.decodeUnknownEffect(HookManifestSchema)(args.manifest.raw, {
68
+ onExcessProperty: "error",
69
+ }).pipe(Effect.mapError(() => new FilteredPackageError({
70
+ code: "reference_invalid",
71
+ detail: "Filtered Hook manifest must declare valid native implementations and safe package-relative references.",
72
+ })));
73
+ for (const file of requiredHookRuntimeFiles(manifest))
74
+ yield* requireEntry(args.entries, file);
82
75
  return;
83
76
  }
84
77
  case "knowledge": {
@@ -35,6 +35,5 @@ export interface PublishInput {
35
35
  readonly clientIntegrity?: string;
36
36
  readonly digestHeader?: string;
37
37
  }
38
- export declare const defaultReadEntry: (archiveBytes: Uint8Array, entry: ZipEntry) => Effect.Effect<Uint8Array, ArchiveGuardrailError>;
39
38
  export declare const normalizePublishInput: (args: NormalizePublishInputArgs) => Effect.Effect<PublishInput, IngestLimitError | IngestUnsupportedContentTypeError | ArchiveGuardrailError | ManifestError | FilteredPackageError>;
40
39
  //# sourceMappingURL=input-normalization.d.ts.map
@@ -1,84 +1,8 @@
1
1
  import * as Effect from "effect/Effect";
2
- import { inflateRawSync } from "node:zlib";
3
- import { ArchiveGuardrailError, ZIP_LOCAL_SIGNATURE, validateArchive, } from "./archive-guardrails.js";
2
+ import { ArchiveGuardrailError, defaultReadEntry, validateArchive, } from "./archive-guardrails.js";
4
3
  import { enforceArchiveContentType, enforceArchiveSizeLimit, } from "./ingest-limits.js";
5
4
  import { resolveManifest, validateDeclaredManifestAlignment, } from "./manifest-policy.js";
6
5
  import { FilteredPackageError, validateFilteredPackage } from "./filtered-package-validation.js";
7
- const LOCAL_FILE_HEADER_SIZE = 30;
8
- const INFLATE_CHUNK_SIZE = 16 * 1024;
9
- export const defaultReadEntry = (archiveBytes, entry) => Effect.gen(function* () {
10
- const { localHeaderOffset, compressedSize, compressionMethod, fileName } = entry;
11
- if (localHeaderOffset + LOCAL_FILE_HEADER_SIZE > archiveBytes.length) {
12
- return yield* new ArchiveGuardrailError({
13
- code: "malformed_archive",
14
- message: `Local file header for entry "${fileName}" exceeds archive bounds.`,
15
- entry: fileName,
16
- });
17
- }
18
- const view = new DataView(archiveBytes.buffer, archiveBytes.byteOffset, archiveBytes.byteLength);
19
- const signature = view.getUint32(localHeaderOffset, true);
20
- if (signature !== ZIP_LOCAL_SIGNATURE) {
21
- return yield* new ArchiveGuardrailError({
22
- code: "malformed_archive",
23
- message: `Invalid local file header signature for entry "${fileName}".`,
24
- entry: fileName,
25
- });
26
- }
27
- const fileNameLength = view.getUint16(localHeaderOffset + 26, true);
28
- const extraFieldLength = view.getUint16(localHeaderOffset + 28, true);
29
- const dataStart = localHeaderOffset + LOCAL_FILE_HEADER_SIZE + fileNameLength + extraFieldLength;
30
- if (dataStart + compressedSize > archiveBytes.length) {
31
- return yield* new ArchiveGuardrailError({
32
- code: "malformed_archive",
33
- message: `Compressed data for entry "${fileName}" exceeds archive bounds.`,
34
- entry: fileName,
35
- });
36
- }
37
- const compressedData = archiveBytes.slice(dataStart, dataStart + compressedSize);
38
- if (compressionMethod === 0) {
39
- return compressedData;
40
- }
41
- if (compressionMethod === 8) {
42
- const expansionFailure = () => new ArchiveGuardrailError({
43
- code: "decompression_limit_exceeded",
44
- message: `Entry "${fileName}" decompresses beyond its declared size of ${entry.uncompressedSize} bytes.`,
45
- entry: fileName,
46
- });
47
- // workerd grows its bounded zlib buffer by whole chunks, including the
48
- // final partial chunk. Reserve one chunk of codec headroom, then enforce
49
- // the exact declared size below. Expansion remains bounded during decode.
50
- const result = yield* Effect.try({
51
- try: () => inflateRawSync(compressedData, {
52
- chunkSize: INFLATE_CHUNK_SIZE,
53
- maxOutputLength: entry.uncompressedSize + INFLATE_CHUNK_SIZE,
54
- }),
55
- catch: (error) => {
56
- const errorCode = typeof error === "object" &&
57
- error !== null &&
58
- "code" in error &&
59
- typeof error.code === "string"
60
- ? error.code
61
- : "";
62
- return errorCode === "ERR_BUFFER_TOO_LARGE" ||
63
- (error instanceof RangeError && error.message === "Memory limit exceeded")
64
- ? expansionFailure()
65
- : new ArchiveGuardrailError({
66
- code: "malformed_archive",
67
- message: `Failed to decompress entry "${fileName}".`,
68
- entry: fileName,
69
- });
70
- },
71
- });
72
- if (result.byteLength > entry.uncompressedSize)
73
- return yield* expansionFailure();
74
- return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
75
- }
76
- return yield* new ArchiveGuardrailError({
77
- code: "unsupported_compression",
78
- message: `Unsupported compression method ${compressionMethod} for entry "${fileName}".`,
79
- entry: fileName,
80
- });
81
- });
82
6
  export const normalizePublishInput = (args) => Effect.gen(function* () {
83
7
  const { declaredIdentity, archive, digestHeader, guardrailLimits } = args;
84
8
  const entryReader = args.readEntry ?? defaultReadEntry;
@@ -39,7 +39,7 @@ export const readExtensionManifest = (directory, type, options) => Effect.gen(fu
39
39
  cause,
40
40
  })));
41
41
  const raw = withDefaultOwner(parsed, options?.defaultOwner);
42
- const manifest = yield* Schema.decodeUnknownEffect(manifestSchemaForType(type))(raw).pipe(Effect.mapError((cause) => new ManifestError({
42
+ const manifest = yield* Schema.decodeUnknownEffect(manifestSchemaForType(type))(raw, type === "hook" ? { onExcessProperty: "error" } : undefined).pipe(Effect.mapError((cause) => new ManifestError({
43
43
  code: "manifest_schema_invalid",
44
44
  detail: `Manifest file "${fileName}" does not conform to the ${type} manifest schema.`,
45
45
  details: SchemaIssue.makeFormatterDefault()(cause.issue),