@inneranimalmedia/agentsam-sdk 2.6.10 → 2.6.11

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 (261) hide show
  1. package/README.md +3 -0
  2. package/TRADEMARKS.md +22 -0
  3. package/apps/README.md +3 -0
  4. package/apps/cad-creator/LICENSE +21 -0
  5. package/apps/cad-creator/README.md +4 -1
  6. package/apps/cad-creator/shared/cad/package.json +1 -1
  7. package/apps/client-cms-editor/LICENSE +21 -0
  8. package/apps/client-cms-editor/README.md +3 -0
  9. package/apps/client-cms-editor/backend/package.json +2 -2
  10. package/apps/client-cms-editor/frontend/package.json +3 -3
  11. package/apps/client-cms-editor/package-lock.json +43 -43
  12. package/apps/client-cms-editor/package.json +3 -3
  13. package/apps/client-cms-editor/reference/harvest/HARVEST_RECEIPT.json +1 -1
  14. package/apps/client-cms-editor/reference/harvest/README.md +1 -1
  15. package/apps/client-cms-editor/reference/harvest/plans/ARCHIVE-INTAKE.md +1 -1
  16. package/apps/client-cms-editor/reference/harvest/plans/HARVEST-INDEX.md +1 -1
  17. package/apps/client-cms-editor/reference/harvest/plans/TOOLING-LAB.md +1 -1
  18. package/apps/client-cms-editor/shared/cms/package.json +1 -1
  19. package/docs/MINI.md +4 -2
  20. package/docs/REPO_FILE_MAP.txt +0 -1
  21. package/docs/SOURCE_ARCHITECTURE.md +1 -1
  22. package/docs/architecture/DUAL_ANALYTICS_PRODUCT.md +140 -0
  23. package/docs/content-studio/REVISION_GATE_2026-09-27.md +1 -1
  24. package/docs/engineering/go-portability/01-product-project-deployment-authority.md +69 -0
  25. package/docs/engineering/provider-internal-renames/01-lane-initialization.md +20 -0
  26. package/docs/engineering/provider-internal-renames/01a-runtime-environment-contract.md +115 -0
  27. package/docs/engineering/provider-internal-renames/02-cfoa.md +57 -0
  28. package/docs/engineering/provider-internal-renames/03-gclioa.md +32 -0
  29. package/docs/engineering/provider-internal-renames/04-goaude.md +50 -0
  30. package/docs/engineering/provider-internal-renames/05-gdrv.md +33 -0
  31. package/docs/engineering/provider-internal-renames/06-verification.md +103 -0
  32. package/docs/engineering/provider-internal-renames/README.md +77 -0
  33. package/docs/engineering/provider-internal-renames/runtime-environment-contract.json +220 -0
  34. package/docs/engineering/test-diagnostics/go-cli-test-hang-20261003.md +49 -0
  35. package/docs/engineering/test-diagnostics/go-skip-deploy-docker-fix.md +90 -0
  36. package/docs/knowledge-service-verification.md +3 -3
  37. package/docs/plans/AGENTSAM-SUSPEND-QUEUE-KNOWLEDGE-RDP-2026-10-03.md +870 -0
  38. package/docs/plans/LOCAL-STUDIO-GRADUATION-2026-09-26.md +9 -9
  39. package/docs/plans/MACHINE-FIRST-APPS-TUI-2026-09-24.md +11 -11
  40. package/docs/plans/RECONCILIATION-2026-09-19.md +6 -6
  41. package/docs/plans/SAM_WORK_PROTOCOL.md +667 -0
  42. package/docs/plans/TOMORROW-EXTENSIVE-TODO-2026-09-26.md +2 -2
  43. package/docs/plans/UNIVERSAL-INGEST-MACHINE-FIRST-2026-09-24.md +3 -3
  44. package/examples/hyperspace-demo/src/main.tsx +227 -110
  45. package/migrations/d1/0020_agentsam_receipt_catalog.sql +35 -0
  46. package/migrations/runtime/0006_agent_run_suspend_activity.sql +53 -0
  47. package/migrations/runtime/0007_queue_control.sql +40 -0
  48. package/migrations/runtime/0008_agent_run_dependencies.sql +24 -0
  49. package/migrations/runtime/0008_analytics_legacy_bootstrap.sql +43 -0
  50. package/migrations/runtime/0009_analytics_authority.sql +108 -0
  51. package/package.json +26 -11
  52. package/packages/agentsam-brand/README.md +3 -0
  53. package/packages/agentsam-brand/package.json +2 -2
  54. package/packages/agentsam-contracts/dist/adapter.d.ts +22 -0
  55. package/packages/agentsam-contracts/dist/adapter.js +0 -0
  56. package/packages/agentsam-contracts/dist/agent.d.ts +66 -0
  57. package/packages/agentsam-contracts/dist/agent.js +15 -0
  58. package/packages/agentsam-contracts/dist/artifacts.d.ts +70 -0
  59. package/packages/agentsam-contracts/dist/artifacts.js +0 -0
  60. package/packages/agentsam-contracts/dist/authority.d.ts +21 -0
  61. package/packages/agentsam-contracts/dist/authority.js +0 -0
  62. package/packages/agentsam-contracts/dist/context.d.ts +14 -0
  63. package/packages/agentsam-contracts/dist/context.js +0 -0
  64. package/packages/agentsam-contracts/dist/errors.d.ts +121 -0
  65. package/packages/agentsam-contracts/dist/errors.js +5 -0
  66. package/packages/agentsam-contracts/dist/events.d.ts +53 -0
  67. package/packages/agentsam-contracts/dist/events.js +0 -0
  68. package/packages/agentsam-contracts/dist/execution.d.ts +38 -0
  69. package/packages/agentsam-contracts/dist/execution.js +0 -0
  70. package/packages/agentsam-contracts/dist/hooks.d.ts +31 -0
  71. package/packages/agentsam-contracts/dist/hooks.js +0 -0
  72. package/packages/agentsam-contracts/dist/identity.d.ts +15 -0
  73. package/packages/agentsam-contracts/dist/identity.js +0 -0
  74. package/packages/agentsam-contracts/dist/index.d.ts +16 -0
  75. package/packages/agentsam-contracts/dist/index.js +164 -0
  76. package/packages/agentsam-contracts/dist/models.d.ts +9 -0
  77. package/packages/agentsam-contracts/dist/models.js +0 -0
  78. package/packages/agentsam-contracts/dist/providers.d.ts +27 -0
  79. package/packages/agentsam-contracts/dist/providers.js +0 -0
  80. package/packages/agentsam-contracts/dist/repository.d.ts +56 -0
  81. package/packages/agentsam-contracts/dist/repository.js +15 -0
  82. package/packages/agentsam-contracts/dist/run.d.ts +113 -0
  83. package/packages/agentsam-contracts/dist/run.js +132 -0
  84. package/packages/agentsam-contracts/dist/tools.d.ts +82 -0
  85. package/packages/agentsam-contracts/dist/tools.js +0 -0
  86. package/packages/agentsam-contracts/dist/widgets.d.ts +38 -0
  87. package/packages/agentsam-contracts/dist/widgets.js +0 -0
  88. package/packages/agentsam-contracts/package.json +9 -1
  89. package/packages/agentsam-contracts/scripts/build.mjs +1 -1
  90. package/packages/agentsam-contracts/src/agent.ts +78 -3
  91. package/packages/agentsam-contracts/src/index.ts +2 -0
  92. package/packages/agentsam-contracts/src/run.ts +320 -0
  93. package/packages/agentsam-contracts/src/widgets.ts +58 -0
  94. package/packages/agentsam-contracts/test/contracts.test.mjs +1 -1
  95. package/packages/agentsam-contracts/test/run-contract.test.mjs +94 -0
  96. package/packages/agentsam-errors/package.json +1 -1
  97. package/packages/agentsam-goap/README.md +3 -0
  98. package/packages/agentsam-goap/package.json +2 -2
  99. package/packages/agentsam-hooks/package.json +2 -2
  100. package/packages/agentsam-hooks/src/adapters/agents.js +48 -22
  101. package/packages/agentsam-hooks/src/index.d.ts +16 -0
  102. package/packages/agentsam-knowledge/package.json +1 -1
  103. package/packages/agentsam-knowledge/src/backends/index.js +1 -1
  104. package/packages/agentsam-merch/README.md +62 -0
  105. package/packages/agentsam-merch/bin/agentsam-merch.mjs +16 -0
  106. package/packages/agentsam-merch/package.json +65 -0
  107. package/packages/agentsam-merch/profiles/completeful/dtg-back-large.json +42 -0
  108. package/packages/agentsam-merch/profiles/completeful/laser-engraving.json +39 -0
  109. package/packages/agentsam-merch/profiles/generic/embroidery-digitization.json +22 -0
  110. package/packages/agentsam-merch/schemas/collection-candidate.schema.json +66 -0
  111. package/packages/agentsam-merch/schemas/manufacturing-profile.schema.json +221 -0
  112. package/packages/agentsam-merch/src/adapters-completeful.js +50 -0
  113. package/packages/agentsam-merch/src/cli.js +93 -0
  114. package/packages/agentsam-merch/src/collection-lab.js +60 -0
  115. package/packages/agentsam-merch/src/compatibility.js +501 -0
  116. package/packages/agentsam-merch/src/derivatives.js +112 -0
  117. package/packages/agentsam-merch/src/index.d.ts +316 -0
  118. package/packages/agentsam-merch/src/index.js +46 -0
  119. package/packages/agentsam-merch/src/node/index.d.ts +58 -0
  120. package/packages/agentsam-merch/src/node/index.js +6 -0
  121. package/packages/agentsam-merch/src/node/raster.js +142 -0
  122. package/packages/agentsam-merch/src/node/vector.js +239 -0
  123. package/packages/agentsam-merch/src/product-concepts.js +118 -0
  124. package/packages/agentsam-merch/src/profile-registry.js +104 -0
  125. package/packages/agentsam-merch/src/profile.js +158 -0
  126. package/packages/agentsam-merch/src/transforms.js +215 -0
  127. package/packages/agentsam-merch/test/merch.test.mjs +332 -0
  128. package/packages/agentsam-merch/test/node-transforms.test.mjs +149 -0
  129. package/packages/agentsam-nav/dist/browser.global.js +71 -34
  130. package/packages/agentsam-nav/dist/browser.js +3179 -781
  131. package/packages/agentsam-nav/dist/index.js +3171 -773
  132. package/packages/agentsam-nav/package.json +1 -1
  133. package/packages/agentsam-queue-control/README.md +4 -1
  134. package/packages/agentsam-queue-control/package.json +4 -3
  135. package/packages/agentsam-queue-control/schemas/job-envelope.schema.json +111 -11
  136. package/packages/agentsam-queue-control/src/adapters/cloudflare.js +34 -6
  137. package/packages/agentsam-queue-control/src/adapters/memory.js +11 -2
  138. package/packages/agentsam-queue-control/src/adapters/sqlite.js +259 -0
  139. package/packages/agentsam-queue-control/src/contracts.js +207 -3
  140. package/packages/agentsam-queue-control/src/control-plane.js +62 -2
  141. package/packages/agentsam-queue-control/src/index.js +1 -0
  142. package/packages/agentsam-queue-control/test/queue-control.test.mjs +310 -0
  143. package/packages/agentsam-queue-control/test/sqlite-adapter.test.mjs +91 -0
  144. package/packages/agentsam-rapid-rust/README.md +3 -0
  145. package/packages/agentsam-repository/README.md +3 -0
  146. package/packages/agentsam-repository/package.json +1 -1
  147. package/packages/agentsam-vault/package.json +1 -1
  148. package/packages/agentsam-workbench/README.md +43 -0
  149. package/packages/agentsam-workbench/package.json +13 -3
  150. package/packages/agentsam-workbench/src/agent/AgentConversationSurface.tsx +108 -0
  151. package/packages/agentsam-workbench/src/agent/AgentPanel.tsx +25 -8
  152. package/packages/agentsam-workbench/src/agent/AgentRuntimeField.tsx +152 -0
  153. package/packages/agentsam-workbench/src/agent/Composer.tsx +10 -1
  154. package/packages/agentsam-workbench/src/agent/RunMode.ts +26 -0
  155. package/packages/agentsam-workbench/src/agent/Thread.tsx +18 -3
  156. package/packages/agentsam-workbench/src/agent/index.ts +3 -0
  157. package/packages/agentsam-workbench/src/index.ts +4 -0
  158. package/packages/agentsam-workbench/src/manufacturing/PreflightIndicator.tsx +203 -0
  159. package/packages/agentsam-workbench/src/manufacturing/index.ts +2 -0
  160. package/packages/agentsam-workbench/src/manufacturing/loading.ts +147 -0
  161. package/packages/agentsam-workbench/src/widgets/BuiltinWidget.tsx +22 -0
  162. package/packages/agentsam-workbench/src/widgets/CountdownWidget.tsx +68 -0
  163. package/packages/agentsam-workbench/src/widgets/WidgetFrame.tsx +56 -0
  164. package/packages/agentsam-workbench/src/widgets/countdown.ts +25 -0
  165. package/packages/agentsam-workbench/src/widgets/index.ts +6 -0
  166. package/packages/agentsam-workbench/src/widgets/registry.ts +13 -0
  167. package/packages/agentsam-workbench/src/widgets/useCountdown.ts +128 -0
  168. package/packages/agentsam-workbench/src/widgets/widgets.css +102 -0
  169. package/packages/connectors/{cloudflare → cfoa}/package.json +3 -3
  170. package/packages/heuristic-theme/package.json +1 -1
  171. package/packages/identity/README.md +3 -0
  172. package/packages/identity/backend/README.md +3 -0
  173. package/packages/identity/package.json +1 -1
  174. package/packages/identity/src/frontend/auth-portal/README.md +3 -0
  175. package/packages/identity/src/index.js +1 -1
  176. package/packages/identity/src/oauth/README.md +4 -1
  177. package/packages/identity/src/oauth/credentials.js +1 -1
  178. package/packages/identity/src/server/worker-router.js +1 -1
  179. package/packages/identity/tests/google-desktop-identity-login.test.mjs +1 -1
  180. package/packages/identity/tests/lane1-packed-consumer.test.mjs +1 -1
  181. package/packages/identity/tests/oauth-presets-accounts-ssot.test.mjs +1 -1
  182. package/packages/providers/completeful/package.json +1 -1
  183. package/packages/theme-church-site/package.json +1 -1
  184. package/packages/theme-companions-site/package.json +1 -1
  185. package/packages/theme-floors-site/package.json +1 -1
  186. package/packages/theme-fuelnfree-site/package.json +1 -1
  187. package/packages/theme-handyman-site/package.json +1 -1
  188. package/packages/theme-insurance-site/package.json +1 -1
  189. package/packages/theme-scenes/package.json +1 -1
  190. package/packages/theme-shinshu-site/package.json +1 -1
  191. package/packages/work-graph/package.json +1 -1
  192. package/protocol/models/README.md +3 -0
  193. package/protocol/receipts/agentsam.receipt.v1.json +95 -0
  194. package/protocol/theme-refinery/receipts/products-registry-batch-20260924.json +6 -6
  195. package/protocol/theme-refinery/receipts/theme-harvest-20260924.json +6 -6
  196. package/skills/README.md +3 -0
  197. package/src/acp/agent-executor.js +106 -0
  198. package/src/acp/client.js +316 -0
  199. package/src/acp/dependencies.js +426 -0
  200. package/src/acp/http.js +207 -0
  201. package/src/acp/index.js +22 -0
  202. package/src/acp/runs.js +125 -0
  203. package/src/acp/runtime-selection.js +180 -0
  204. package/src/cli/command-catalog.js +3 -0
  205. package/src/cli.js +24 -0
  206. package/src/commands/cloudflare-login.js +1 -1
  207. package/src/commands/cloudflare.js +2 -2
  208. package/src/commands/connections.js +1 -1
  209. package/src/commands/go.js +76 -6
  210. package/src/commands/merch.js +5 -0
  211. package/src/commands/mini.js +1 -1
  212. package/src/commands/receipts.js +140 -0
  213. package/src/commands/run.js +253 -0
  214. package/src/go/container.js +1 -1
  215. package/src/go/contract.js +61 -18
  216. package/src/go/discover.js +0 -4
  217. package/src/go/index.js +13 -1
  218. package/src/go/probe.js +109 -0
  219. package/src/go/{cloudflare.js → providers/cloudflare.js} +36 -136
  220. package/src/go/verify.js +1 -1
  221. package/src/indexing/ingest/inventory.js +1 -1
  222. package/src/lib/deploy/local-studio.js +2 -2
  223. package/src/lib/mini/scaffold.js +1 -1
  224. package/src/lib/whoami-capabilities.js +1 -1
  225. package/src/local/runtime-store.js +8 -0
  226. package/src/mcp/cloudflare-bundles.js +1 -1
  227. package/src/plugins/index.js +1 -0
  228. package/src/plugins/registry.js +59 -6
  229. package/src/receipts/contract.js +68 -0
  230. package/src/receipts/index.js +20 -0
  231. package/src/receipts/prune.js +64 -0
  232. package/src/receipts/sinks.js +285 -0
  233. package/templates/mini/gadget/app.js +15 -40
  234. package/templates/mini/gadget/index.html +7 -7
  235. package/templates/mini/shared/style.css +1 -1
  236. package/packages/agentsam-repository/src/goap.js.bak-20260922011251 +0 -670
  237. /package/packages/connectors/{cloudflare → cfoa}/OAUTH_STORAGE_AUDIT.md +0 -0
  238. /package/packages/connectors/{cloudflare → cfoa}/src/api-client.js +0 -0
  239. /package/packages/connectors/{cloudflare → cfoa}/src/capabilities.js +0 -0
  240. /package/packages/connectors/{cloudflare → cfoa}/src/credential.js +0 -0
  241. /package/packages/connectors/{cloudflare → cfoa}/src/families/brand-protection.js +0 -0
  242. /package/packages/connectors/{cloudflare → cfoa}/src/families/keyless.js +0 -0
  243. /package/packages/connectors/{cloudflare → cfoa}/src/families/pages.js +0 -0
  244. /package/packages/connectors/{cloudflare → cfoa}/src/families/scanner.js +0 -0
  245. /package/packages/connectors/{cloudflare → cfoa}/src/families/snippets.js +0 -0
  246. /package/packages/connectors/{cloudflare → cfoa}/src/families/tag-gateway.js +0 -0
  247. /package/packages/connectors/{cloudflare → cfoa}/src/families/tags.js +0 -0
  248. /package/packages/connectors/{cloudflare → cfoa}/src/families/token-validation.js +0 -0
  249. /package/packages/connectors/{cloudflare → cfoa}/src/families/vectorize.js +0 -0
  250. /package/packages/connectors/{cloudflare → cfoa}/src/families/workflows.js +0 -0
  251. /package/packages/connectors/{cloudflare → cfoa}/src/index.js +0 -0
  252. /package/packages/connectors/{cloudflare → cfoa}/src/oauth-persist.js +0 -0
  253. /package/packages/connectors/{cloudflare → cfoa}/src/owner.js +0 -0
  254. /package/packages/connectors/{cloudflare → cfoa}/src/permission-options.js +0 -0
  255. /package/packages/connectors/{cloudflare → cfoa}/src/routes.js +0 -0
  256. /package/packages/connectors/{cloudflare → cfoa}/src/scope-catalog.js +0 -0
  257. /package/packages/connectors/{cloudflare → cfoa}/src/vault.js +0 -0
  258. /package/packages/connectors/{cloudflare → cfoa}/tests/capabilities.test.mjs +0 -0
  259. /package/packages/connectors/{cloudflare → cfoa}/tests/connector.test.mjs +0 -0
  260. /package/packages/connectors/{cloudflare → cfoa}/tests/oauth-persist.test.mjs +0 -0
  261. /package/packages/identity/src/oauth/{google-desktop-exchange.js → goaude.js} +0 -0
@@ -0,0 +1,215 @@
1
+ import {
2
+ COMPATIBILITY_STATUS,
3
+ evaluateManufacturingCompatibility,
4
+ } from "./compatibility.js";
5
+ import { normalizeManufacturingProfile } from "./profile.js";
6
+
7
+ export const TRANSFORM_OPERATION = Object.freeze({
8
+ TRIM_ALPHA: "trim_alpha",
9
+ NORMALIZE_COLOR_SPACE: "normalize_color_space",
10
+ NORMALIZE_SVG: "normalize_svg",
11
+ OUTLINE_TEXT: "outline_text",
12
+ VECTORIZER: "vectorize_monochrome",
13
+ RASTERIZE: "rasterize",
14
+ LOSSLESS_OPTIMIZE: "lossless_optimize",
15
+ CONVERT_FORMAT: "convert_format",
16
+ CONVERT_MONOCHROME: "convert_monochrome",
17
+ });
18
+
19
+ function operationFromAction(action, issue, profile) {
20
+ if (!action) return null;
21
+ if (action === "trim_alpha") {
22
+ return {
23
+ id: TRANSFORM_OPERATION.TRIM_ALPHA,
24
+ automatic: true,
25
+ requires: ["raster_normalizer"],
26
+ params: {},
27
+ };
28
+ }
29
+ if (action.startsWith("normalize_color_space:")) {
30
+ return {
31
+ id: TRANSFORM_OPERATION.NORMALIZE_COLOR_SPACE,
32
+ automatic: true,
33
+ requires: ["raster_normalizer"],
34
+ params: { colorSpace: action.split(":")[1] || profile.colorSpace || "srgb" },
35
+ };
36
+ }
37
+ if (action === "normalize_svg") {
38
+ return {
39
+ id: TRANSFORM_OPERATION.NORMALIZE_SVG,
40
+ automatic: true,
41
+ requires: ["svg_normalizer"],
42
+ params: { requireViewBox: true },
43
+ };
44
+ }
45
+ if (action === "outline_text") {
46
+ return {
47
+ id: TRANSFORM_OPERATION.OUTLINE_TEXT,
48
+ automatic: true,
49
+ requires: ["text_outliner"],
50
+ params: {},
51
+ note:
52
+ "SVGO does not convert fonts to paths by itself; outlining requires a font-aware vector executor.",
53
+ };
54
+ }
55
+ if (action === "vectorize_monochrome") {
56
+ return {
57
+ id: TRANSFORM_OPERATION.VECTORIZER,
58
+ automatic: true,
59
+ requires: ["vector_tracer", "raster_normalizer"],
60
+ params: {
61
+ tracer: issue?.meta?.preferredTracer || profile.vectorization.preferredTracer || "auto",
62
+ outputFormat: profile.vectorization.outputFormat || "svg",
63
+ maxAspectDrift: profile.vectorization.maxAspectDrift,
64
+ minBoundaryIntegrity: profile.vectorization.minBoundaryIntegrity,
65
+ },
66
+ };
67
+ }
68
+ if (action.startsWith("rasterize:")) {
69
+ return {
70
+ id: TRANSFORM_OPERATION.RASTERIZE,
71
+ automatic: true,
72
+ requires: ["vector_rasterizer"],
73
+ params: { format: action.split(":")[1] || profile.format },
74
+ };
75
+ }
76
+ if (action === "lossless_optimize") {
77
+ return {
78
+ id: TRANSFORM_OPERATION.LOSSLESS_OPTIMIZE,
79
+ automatic: true,
80
+ requires: ["raster_normalizer"],
81
+ params: { format: profile.format },
82
+ };
83
+ }
84
+ if (action.startsWith("convert:")) {
85
+ return {
86
+ id: TRANSFORM_OPERATION.CONVERT_FORMAT,
87
+ automatic: true,
88
+ requires: ["format_converter"],
89
+ params: { format: action.split(":")[1] || profile.format },
90
+ };
91
+ }
92
+ if (action === "convert_monochrome") {
93
+ return {
94
+ id: TRANSFORM_OPERATION.CONVERT_MONOCHROME,
95
+ automatic: true,
96
+ requires: ["vector_normalizer"],
97
+ params: {},
98
+ };
99
+ }
100
+ return {
101
+ id: action,
102
+ automatic: false,
103
+ requires: [],
104
+ params: {},
105
+ };
106
+ }
107
+
108
+ export function planAutomaticTransforms({
109
+ asset,
110
+ profile: profileInput,
111
+ target = {},
112
+ capabilities = {},
113
+ } = {}) {
114
+ const profile = normalizeManufacturingProfile(profileInput);
115
+ const compatibility = evaluateManufacturingCompatibility(asset, profile, target);
116
+ const operations = [];
117
+ const seen = new Set();
118
+
119
+ for (const issue of compatibility.issues) {
120
+ const operation = operationFromAction(issue.action, issue, profile);
121
+ if (!operation || seen.has(operation.id)) continue;
122
+ seen.add(operation.id);
123
+ const missingCapabilities = operation.requires.filter(
124
+ (capability) => capabilities[capability] !== true,
125
+ );
126
+ operations.push(
127
+ Object.freeze({
128
+ ...operation,
129
+ issueCode: issue.code,
130
+ missingCapabilities: Object.freeze(missingCapabilities),
131
+ executable:
132
+ operation.automatic === true && missingCapabilities.length === 0,
133
+ }),
134
+ );
135
+ }
136
+
137
+ const blockingIssue = compatibility.issues.find(
138
+ (issue) =>
139
+ issue.status === COMPATIBILITY_STATUS.NEEDS_VARIANT ||
140
+ issue.status === COMPATIBILITY_STATUS.UNSUPPORTED,
141
+ );
142
+ const allRequiredExecutable = operations
143
+ .filter((operation) => operation.automatic)
144
+ .every((operation) => operation.executable);
145
+
146
+ return Object.freeze({
147
+ compatibility,
148
+ operations: Object.freeze(operations),
149
+ canExecuteAutomatically:
150
+ !blockingIssue && operations.length > 0 && allRequiredExecutable,
151
+ hasBlockingIssue: Boolean(blockingIssue),
152
+ });
153
+ }
154
+
155
+ function ratio(width, height) {
156
+ const w = Number(width);
157
+ const h = Number(height);
158
+ return Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0
159
+ ? w / h
160
+ : null;
161
+ }
162
+
163
+ export function verifyVectorizationResult({
164
+ source,
165
+ result,
166
+ profile: profileInput,
167
+ } = {}) {
168
+ const profile = normalizeManufacturingProfile(profileInput);
169
+ const sourceRatio = ratio(
170
+ source?.contentWidthPx || source?.widthPx || source?.width,
171
+ source?.contentHeightPx || source?.heightPx || source?.height,
172
+ );
173
+ const resultRatio = ratio(
174
+ result?.contentWidthPx || result?.widthPx || result?.width,
175
+ result?.contentHeightPx || result?.heightPx || result?.height,
176
+ );
177
+
178
+ const aspectDrift =
179
+ sourceRatio && resultRatio
180
+ ? Math.abs(resultRatio - sourceRatio) / Math.abs(sourceRatio)
181
+ : null;
182
+ const boundaryIntegrity =
183
+ Number.isFinite(Number(result?.boundaryIntegrity))
184
+ ? Math.max(0, Math.min(1, Number(result.boundaryIntegrity)))
185
+ : null;
186
+
187
+ const aspectOk =
188
+ aspectDrift != null &&
189
+ aspectDrift <= profile.vectorization.maxAspectDrift;
190
+ const boundaryOk =
191
+ boundaryIntegrity != null &&
192
+ boundaryIntegrity >= profile.vectorization.minBoundaryIntegrity;
193
+
194
+ return Object.freeze({
195
+ verified: aspectOk && boundaryOk,
196
+ aspectOk,
197
+ boundaryOk,
198
+ aspectDrift,
199
+ boundaryIntegrity,
200
+ thresholds: Object.freeze({
201
+ maxAspectDrift: profile.vectorization.maxAspectDrift,
202
+ minBoundaryIntegrity: profile.vectorization.minBoundaryIntegrity,
203
+ }),
204
+ reason:
205
+ aspectDrift == null
206
+ ? "aspect_ratio_unavailable"
207
+ : !aspectOk
208
+ ? "aspect_ratio_drift"
209
+ : boundaryIntegrity == null
210
+ ? "boundary_integrity_unavailable"
211
+ : !boundaryOk
212
+ ? "boundary_integrity_below_threshold"
213
+ : null,
214
+ });
215
+ }
@@ -0,0 +1,332 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFile } from "node:fs/promises";
3
+ import test from "node:test";
4
+ import {
5
+ COLLECTION_CANDIDATE_STATE,
6
+ COMPATIBILITY_STATUS,
7
+ approvalsComplete,
8
+ calculateEffectivePpi,
9
+ createCollectionCandidate,
10
+ createManufacturingProfileRegistry,
11
+ createProductConceptRegistry,
12
+ evaluateManufacturingCompatibility,
13
+ planAutomaticTransforms,
14
+ planMediaDerivatives,
15
+ resolveProductConceptTarget,
16
+ transitionCollectionCandidate,
17
+ validateManufacturingProfile,
18
+ verifyVectorizationResult,
19
+ } from "../src/index.js";
20
+
21
+ async function profile(name) {
22
+ return JSON.parse(await readFile(new URL(`../profiles/${name}`, import.meta.url), "utf8"));
23
+ }
24
+
25
+ test("profile data validates", async () => {
26
+ for (const name of [
27
+ "completeful/dtg-back-large.json",
28
+ "completeful/laser-engraving.json",
29
+ "generic/embroidery-digitization.json",
30
+ ]) {
31
+ const result = validateManufacturingProfile(await profile(name));
32
+ assert.equal(result.ok, true, `${name}: ${result.errors.join(", ")}`);
33
+ }
34
+ });
35
+
36
+ test("large transparent PNG is ready for the verified Completeful DTG profile", async () => {
37
+ const result = evaluateManufacturingCompatibility(
38
+ {
39
+ format: "png",
40
+ mediaKind: "raster",
41
+ widthPx: 4500,
42
+ heightPx: 5100,
43
+ bytes: 12000000,
44
+ colorSpace: "srgb",
45
+ hasAlpha: true
46
+ },
47
+ await profile("completeful/dtg-back-large.json"),
48
+ { printWidthIn: 14, printHeightIn: 16 },
49
+ );
50
+ assert.equal(result.status, COMPATIBILITY_STATUS.READY);
51
+ assert.equal(result.requirementsVerified, true);
52
+ assert.equal(result.productionReady, true);
53
+ assert.ok(result.effectivePpi >= 300);
54
+ });
55
+
56
+ test("low-resolution raster is not falsely called production ready", async () => {
57
+ const result = evaluateManufacturingCompatibility(
58
+ { format: "png", mediaKind: "raster", widthPx: 1500, heightPx: 1500, colorSpace: "srgb" },
59
+ await profile("completeful/dtg-back-large.json"),
60
+ { printWidthIn: 14, printHeightIn: 16 },
61
+ );
62
+ assert.equal(result.status, COMPATIBILITY_STATUS.NEEDS_VARIANT);
63
+ assert.ok(result.issues.some((issue) => issue.code === "insufficient_resolution"));
64
+ });
65
+
66
+ test("engraving rejects gradient/open-path artwork", async () => {
67
+ const result = evaluateManufacturingCompatibility(
68
+ {
69
+ format: "svg",
70
+ mediaKind: "vector",
71
+ colorMode: "full-color",
72
+ hasGradients: true,
73
+ hasOpenPaths: true,
74
+ hasLiveText: true
75
+ },
76
+ await profile("completeful/laser-engraving.json"),
77
+ );
78
+ assert.equal(result.status, COMPATIBILITY_STATUS.NEEDS_VARIANT);
79
+ assert.ok(result.actions.includes("simplify_gradients"));
80
+ assert.ok(result.actions.includes("close_paths"));
81
+ });
82
+
83
+ test("embroidery stops at prepared for digitization", async () => {
84
+ const result = evaluateManufacturingCompatibility(
85
+ {
86
+ format: "svg",
87
+ mediaKind: "vector",
88
+ colorMode: "spot",
89
+ hasGradients: false,
90
+ hasOpenPaths: false,
91
+ hasLiveText: false
92
+ },
93
+ await profile("generic/embroidery-digitization.json"),
94
+ );
95
+ assert.equal(result.status, COMPATIBILITY_STATUS.PREPARED_FOR_DIGITIZATION);
96
+ assert.equal(result.productionReady, false);
97
+ });
98
+
99
+ test("manufacturing and storefront derivatives remain separate", async () => {
100
+ const plan = planMediaDerivatives({
101
+ asset: { format: "png", mediaKind: "raster", widthPx: 4500, heightPx: 5100, colorSpace: "srgb" },
102
+ profile: await profile("completeful/dtg-back-large.json"),
103
+ target: { printWidthIn: 14, printHeightIn: 16 },
104
+ });
105
+ assert.equal(plan.source.immutableAuthority, true);
106
+ assert.equal(plan.manufacturing.format, "png");
107
+ assert.equal(plan.storefront.format, "webp");
108
+ assert.equal(plan.manufacturing.mustNotUseStorefrontDerivative, true);
109
+ });
110
+
111
+ test("profile registry selects using data selectors", async () => {
112
+ const registry = createManufacturingProfileRegistry([
113
+ await profile("completeful/dtg-back-large.json"),
114
+ await profile("completeful/laser-engraving.json"),
115
+ ]);
116
+ assert.equal(
117
+ registry.select({
118
+ manufacturer: "completeful",
119
+ process: "dtg",
120
+ locationName: "Back",
121
+ printWidthIn: 14,
122
+ })?.id,
123
+ "completeful.dtg.back-large",
124
+ );
125
+ });
126
+
127
+ test("collection lab uses explicit approvals and valid transitions", () => {
128
+ const concept = createCollectionCandidate({
129
+ id: "burn-it-gt-back-tee",
130
+ designId: "burn-it-gt",
131
+ collectionPath: ["Fuel & Free Time", "Performance", "Burn It GT"],
132
+ approvals: { artwork: true, wording: true, palette: true, visualIdentity: true },
133
+ });
134
+ assert.equal(approvalsComplete(concept), true);
135
+ const approved = transitionCollectionCandidate(
136
+ concept,
137
+ COLLECTION_CANDIDATE_STATE.APPROVED_ART,
138
+ );
139
+ assert.equal(approved.state, COLLECTION_CANDIDATE_STATE.APPROVED_ART);
140
+ assert.throws(() =>
141
+ transitionCollectionCandidate(approved, COLLECTION_CANDIDATE_STATE.PUBLISHED),
142
+ );
143
+ });
144
+
145
+
146
+ test("effective PPI follows actual physical placement geometry", () => {
147
+ const profile = {
148
+ id: "test.dtg",
149
+ manufacturer: "test",
150
+ process: "dtg",
151
+ format: "png",
152
+ allowRaster: true,
153
+ resolution: { readyPpi: 300, minimumPpi: 200 },
154
+ source: { verified: true },
155
+ };
156
+
157
+ const ready = evaluateManufacturingCompatibility(
158
+ { format: "png", widthPx: 3000, heightPx: 3600, colorSpace: "srgb" },
159
+ profile,
160
+ { placementWidthIn: 10, placementHeightIn: 12 },
161
+ );
162
+ assert.equal(ready.effectivePpi, 300);
163
+ assert.equal(ready.status, COMPATIBILITY_STATUS.READY);
164
+
165
+ const warning = evaluateManufacturingCompatibility(
166
+ { format: "png", widthPx: 2400, heightPx: 2880, colorSpace: "srgb" },
167
+ profile,
168
+ { placementWidthIn: 10, placementHeightIn: 12 },
169
+ );
170
+ assert.equal(warning.effectivePpi, 240);
171
+ assert.equal(warning.status, COMPATIBILITY_STATUS.READY_WITH_WARNING);
172
+ assert.equal(warning.productionReady, true);
173
+
174
+ const fail = evaluateManufacturingCompatibility(
175
+ { format: "png", widthPx: 1500, heightPx: 1800, colorSpace: "srgb" },
176
+ profile,
177
+ { placementWidthIn: 10, placementHeightIn: 12 },
178
+ );
179
+ assert.equal(fail.effectivePpi, 150);
180
+ assert.equal(fail.status, COMPATIBILITY_STATUS.NEEDS_VARIANT);
181
+ });
182
+
183
+ test("content bounds, not transparent canvas bounds, drive effective PPI", () => {
184
+ const profile = {
185
+ id: "test.dtg.trim",
186
+ manufacturer: "test",
187
+ process: "dtg",
188
+ format: "png",
189
+ allowRaster: true,
190
+ resolution: { readyPpi: 300, minimumPpi: 200 },
191
+ autoNormalize: { trimAlpha: true },
192
+ };
193
+ const ppi = calculateEffectivePpi(
194
+ {
195
+ format: "png",
196
+ widthPx: 5000,
197
+ heightPx: 5000,
198
+ contentWidthPx: 2400,
199
+ contentHeightPx: 3000,
200
+ transparentBorderDetected: true,
201
+ isAlphaTrimmed: false,
202
+ },
203
+ profile,
204
+ { placementWidthIn: 10, placementHeightIn: 10 },
205
+ );
206
+ assert.equal(ppi.basis, "content_bounds");
207
+ assert.equal(ppi.effectivePpi, 240);
208
+
209
+ const compatibility = evaluateManufacturingCompatibility(
210
+ {
211
+ format: "png",
212
+ widthPx: 5000,
213
+ heightPx: 5000,
214
+ contentWidthPx: 2400,
215
+ contentHeightPx: 3000,
216
+ transparentBorderDetected: true,
217
+ isAlphaTrimmed: false,
218
+ },
219
+ profile,
220
+ { placementWidthIn: 10, placementHeightIn: 10 },
221
+ );
222
+ assert.ok(compatibility.actions.includes("trim_alpha"));
223
+ assert.equal(compatibility.status, COMPATIBILITY_STATUS.READY_WITH_TRANSFORM);
224
+ });
225
+
226
+ test("engraving raster can be auto-vectorized but is never silently called ready", async () => {
227
+ const engraving = await profile("completeful/laser-engraving.json");
228
+ const compatibility = evaluateManufacturingCompatibility(
229
+ {
230
+ format: "png",
231
+ mediaKind: "raster",
232
+ widthPx: 3000,
233
+ heightPx: 3000,
234
+ colorMode: "full-color",
235
+ },
236
+ engraving,
237
+ );
238
+ assert.equal(compatibility.status, COMPATIBILITY_STATUS.READY_WITH_TRANSFORM);
239
+ assert.ok(compatibility.actions.includes("vectorize_monochrome"));
240
+
241
+ const plan = planAutomaticTransforms({
242
+ asset: {
243
+ format: "png",
244
+ mediaKind: "raster",
245
+ widthPx: 3000,
246
+ heightPx: 3000,
247
+ colorMode: "full-color",
248
+ },
249
+ profile: engraving,
250
+ capabilities: {
251
+ vector_tracer: true,
252
+ raster_normalizer: true,
253
+ },
254
+ });
255
+ assert.ok(plan.operations.some((operation) => operation.id === "vectorize_monochrome"));
256
+ assert.ok(plan.operations.every((operation) => operation.id !== "vectorize_monochrome" || operation.executable));
257
+ });
258
+
259
+ test("vectorization result must preserve aspect ratio and boundary integrity", async () => {
260
+ const engraving = await profile("completeful/laser-engraving.json");
261
+ const good = verifyVectorizationResult({
262
+ source: { widthPx: 1000, heightPx: 500 },
263
+ result: { widthPx: 1000, heightPx: 500, boundaryIntegrity: 0.995 },
264
+ profile: engraving,
265
+ });
266
+ assert.equal(good.verified, true);
267
+
268
+ const bad = verifyVectorizationResult({
269
+ source: { widthPx: 1000, heightPx: 500 },
270
+ result: { widthPx: 900, heightPx: 500, boundaryIntegrity: 0.91 },
271
+ profile: engraving,
272
+ });
273
+ assert.equal(bad.verified, false);
274
+ assert.ok(["aspect_ratio_drift", "boundary_integrity_below_threshold"].includes(bad.reason));
275
+ });
276
+
277
+ test("product concepts select providers without baking provider logic into the engine", () => {
278
+ const profiles = createManufacturingProfileRegistry([
279
+ {
280
+ id: "provider-a.dtg.standard",
281
+ manufacturer: "provider-a",
282
+ process: "dtg",
283
+ format: "png",
284
+ allowRaster: true,
285
+ },
286
+ {
287
+ id: "provider-b.dtg.standard",
288
+ manufacturer: "provider-b",
289
+ process: "dtg",
290
+ format: "png",
291
+ allowRaster: true,
292
+ },
293
+ ]);
294
+ const concepts = createProductConceptRegistry([
295
+ {
296
+ id: "heavyweight-graphic-tee",
297
+ label: "Heavyweight Graphic Tee",
298
+ targets: [
299
+ { provider: "provider-a", profileId: "provider-a.dtg.standard", priority: 0 },
300
+ { provider: "provider-b", profileId: "provider-b.dtg.standard", priority: 10 },
301
+ ],
302
+ },
303
+ ]);
304
+ const resolved = resolveProductConceptTarget(
305
+ concepts.get("heavyweight-graphic-tee"),
306
+ {
307
+ profileRegistry: profiles,
308
+ availableProviders: ["provider-b"],
309
+ },
310
+ );
311
+ assert.equal(resolved.selected.provider, "provider-b");
312
+ assert.equal(resolved.selected.profileId, "provider-b.dtg.standard");
313
+ });
314
+
315
+ test("missing provider warning threshold fails closed instead of inventing one", () => {
316
+ const profile = {
317
+ id: "test.strict",
318
+ manufacturer: "test",
319
+ process: "dtg",
320
+ format: "png",
321
+ allowRaster: true,
322
+ ppi: 300,
323
+ source: { verified: true },
324
+ };
325
+ const result = evaluateManufacturingCompatibility(
326
+ { format: "png", widthPx: 2500, heightPx: 2500 },
327
+ profile,
328
+ { placementWidthIn: 10, placementHeightIn: 10 },
329
+ );
330
+ assert.equal(result.status, COMPATIBILITY_STATUS.NEEDS_VARIANT);
331
+ assert.equal(result.resolution.minimumPpi, 300);
332
+ });
@@ -0,0 +1,149 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import test from "node:test";
5
+
6
+ import {
7
+ detectNodeTransformCapabilities,
8
+ inspectRaster,
9
+ normalizeRaster,
10
+ traceRasterToSvg,
11
+ } from "../src/node/index.js";
12
+ import { verifyVectorizationResult } from "../src/index.js";
13
+
14
+ const execFileAsync = promisify(execFile);
15
+
16
+ async function sharpModule() {
17
+ try {
18
+ const mod = await import("sharp");
19
+ return mod.default || mod;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ async function makeTransparentFixture(sharp) {
26
+ const base = await sharp({
27
+ create: {
28
+ width: 400,
29
+ height: 300,
30
+ channels: 4,
31
+ background: { r: 0, g: 0, b: 0, alpha: 0 },
32
+ },
33
+ })
34
+ .composite([
35
+ {
36
+ input: {
37
+ create: {
38
+ width: 240,
39
+ height: 180,
40
+ channels: 4,
41
+ background: { r: 20, g: 20, b: 20, alpha: 1 },
42
+ },
43
+ },
44
+ left: 80,
45
+ top: 60,
46
+ },
47
+ ])
48
+ .png()
49
+ .toBuffer();
50
+ return base;
51
+ }
52
+
53
+ test("node raster inspector measures visible alpha bounds", async (t) => {
54
+ const sharp = await sharpModule();
55
+ if (!sharp) return t.skip("sharp unavailable");
56
+ const source = await makeTransparentFixture(sharp);
57
+ const descriptor = await inspectRaster(source, { sharp });
58
+ assert.equal(descriptor.widthPx, 400);
59
+ assert.equal(descriptor.heightPx, 300);
60
+ assert.equal(descriptor.contentWidthPx, 240);
61
+ assert.equal(descriptor.contentHeightPx, 180);
62
+ assert.equal(descriptor.transparentBorderDetected, true);
63
+ });
64
+
65
+ test("node raster normalizer trims alpha without upscaling", async (t) => {
66
+ const sharp = await sharpModule();
67
+ if (!sharp) return t.skip("sharp unavailable");
68
+ const source = await makeTransparentFixture(sharp);
69
+ const result = await normalizeRaster(source, {
70
+ sharp,
71
+ format: "png",
72
+ operations: [
73
+ { id: "trim_alpha", params: {} },
74
+ { id: "normalize_color_space", params: { colorSpace: "srgb" } },
75
+ ],
76
+ });
77
+ assert.equal(result.descriptor.widthPx, 240);
78
+ assert.equal(result.descriptor.heightPx, 180);
79
+ assert.equal(result.descriptor.colorSpace, "srgb");
80
+ });
81
+
82
+ test("installed potrace produces a verifiable monochrome SVG derivative", async (t) => {
83
+ const sharp = await sharpModule();
84
+ if (!sharp) return t.skip("sharp unavailable");
85
+ try {
86
+ await execFileAsync("potrace", ["--version"]);
87
+ } catch {
88
+ return t.skip("potrace unavailable");
89
+ }
90
+
91
+ const source = await sharp({
92
+ create: {
93
+ width: 300,
94
+ height: 180,
95
+ channels: 3,
96
+ background: "#ffffff",
97
+ },
98
+ })
99
+ .composite([
100
+ {
101
+ input: {
102
+ create: {
103
+ width: 180,
104
+ height: 90,
105
+ channels: 3,
106
+ background: "#000000",
107
+ },
108
+ },
109
+ left: 60,
110
+ top: 45,
111
+ },
112
+ ])
113
+ .png()
114
+ .toBuffer();
115
+
116
+ const traced = await traceRasterToSvg(source, { tracer: "potrace", sharp });
117
+ assert.equal(traced.tracer, "potrace");
118
+ assert.match(traced.svg, /<svg\b/);
119
+ assert.ok(traced.metrics.boundaryIntegrity > 0.9);
120
+
121
+ const verification = verifyVectorizationResult({
122
+ source: { widthPx: 300, heightPx: 180 },
123
+ result: {
124
+ widthPx: traced.metrics.width,
125
+ heightPx: traced.metrics.height,
126
+ boundaryIntegrity: traced.metrics.boundaryIntegrity,
127
+ },
128
+ profile: {
129
+ id: "local.engraving",
130
+ manufacturer: "local",
131
+ process: "engraving",
132
+ format: "svg",
133
+ allowVector: true,
134
+ vectorization: {
135
+ enabled: true,
136
+ maxAspectDrift: 0.02,
137
+ minBoundaryIntegrity: 0.9,
138
+ },
139
+ },
140
+ });
141
+ assert.equal(verification.verified, true);
142
+ });
143
+
144
+ test("capability probe reports machine transforms without inventing unavailable tools", async () => {
145
+ const capabilities = await detectNodeTransformCapabilities();
146
+ assert.equal(typeof capabilities.raster_normalizer, "boolean");
147
+ assert.equal(typeof capabilities.vector_tracer, "boolean");
148
+ assert.equal(typeof capabilities.tools.svgo, "boolean");
149
+ });